no message
This commit is contained in:
parent
4c51e52a30
commit
6981ee03bf
@ -105,7 +105,10 @@ class DialogController extends AbstractController
|
|||||||
return $item;
|
return $item;
|
||||||
});
|
});
|
||||||
//
|
//
|
||||||
return Base::retSuccess('success', $list);
|
$data = $list->toArray();
|
||||||
|
$data['dialog'] = WebSocketDialog::formatData($dialog, $user->userid);
|
||||||
|
//
|
||||||
|
return Base::retSuccess('success', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,10 +44,13 @@ class WebSocketDialog extends AbstractModel
|
|||||||
$dialog->last_msg = $last_msg;
|
$dialog->last_msg = $last_msg;
|
||||||
// 未读信息
|
// 未读信息
|
||||||
$dialog->unread = WebSocketDialogMsgRead::whereDialogId($dialog->id)->whereUserid($userid)->whereReadAt(null)->count();
|
$dialog->unread = WebSocketDialogMsgRead::whereDialogId($dialog->id)->whereUserid($userid)->whereReadAt(null)->count();
|
||||||
|
// 对话人数
|
||||||
|
$builder = WebSocketDialogUser::whereDialogId($dialog->id);
|
||||||
|
$dialog->people = $builder->count();
|
||||||
// 对方信息
|
// 对方信息
|
||||||
$dialog->dialog_user = null;
|
$dialog->dialog_user = null;
|
||||||
if ($dialog->type === 'user') {
|
if ($dialog->type === 'user') {
|
||||||
$dialog_user = WebSocketDialogUser::whereDialogId($dialog->id)->where('userid', '!=', $userid)->first();
|
$dialog_user = $builder->where('userid', '!=', $userid)->first();
|
||||||
$dialog->name = User::userid2nickname($dialog_user->userid);
|
$dialog->name = User::userid2nickname($dialog_user->userid);
|
||||||
$dialog->dialog_user = $dialog_user;
|
$dialog->dialog_user = $dialog_user;
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ services:
|
|||||||
- ./docker/php/php.ini:/usr/local/etc/php/php.ini
|
- ./docker/php/php.ini:/usr/local/etc/php/php.ini
|
||||||
- ./docker/log/supervisor:/var/log/supervisor
|
- ./docker/log/supervisor:/var/log/supervisor
|
||||||
- ./:/var/www
|
- ./:/var/www
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
environment:
|
environment:
|
||||||
TZ: "Asia/Shanghai"
|
TZ: "Asia/Shanghai"
|
||||||
LANG: "C.UTF-8"
|
LANG: "C.UTF-8"
|
||||||
@ -38,6 +39,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./docker/nginx:/etc/nginx/conf.d
|
- ./docker/nginx:/etc/nginx/conf.d
|
||||||
- ./public:/var/www/public
|
- ./public:/var/www/public
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
environment:
|
environment:
|
||||||
TZ: "Asia/Shanghai"
|
TZ: "Asia/Shanghai"
|
||||||
networks:
|
networks:
|
||||||
@ -65,6 +67,7 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- ./docker/mysql/conf.d:/etc/mysql/conf.d
|
- ./docker/mysql/conf.d:/etc/mysql/conf.d
|
||||||
- ./docker/mysql/data:/var/lib/mysql
|
- ./docker/mysql/data:/var/lib/mysql
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
environment:
|
environment:
|
||||||
TZ: "Asia/Shanghai"
|
TZ: "Asia/Shanghai"
|
||||||
MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASSWORD}"
|
MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASSWORD}"
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="scrollerView" class="app-scroller" :class="[static ? 'app-scroller-static' : '']">
|
<div ref="scrollerView" class="app-scroller" :class="[static ? 'app-scroller-static' : '']">
|
||||||
<slot/>
|
<slot/>
|
||||||
|
<div ref="bottom" class="app-scroller-bottom"></div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -14,6 +15,11 @@
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
|
.app-scroller-bottom {
|
||||||
|
height: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-scroller-static {
|
.app-scroller-static {
|
||||||
@ -29,16 +35,49 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
autoBottom: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
autoRecovery: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
autoRecoveryAnimate: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
scrollY: 0,
|
scrollY: 0,
|
||||||
scrollDiff: 0,
|
scrollDiff: 0,
|
||||||
scrollInfo: {},
|
autoInterval: null,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$nextTick(() => {
|
this.openInterval()
|
||||||
|
this.$nextTick(this.initScroll);
|
||||||
|
},
|
||||||
|
|
||||||
|
activated() {
|
||||||
|
this.openInterval()
|
||||||
|
this.recoveryScroll()
|
||||||
|
},
|
||||||
|
|
||||||
|
destroyed() {
|
||||||
|
this.closeInterval()
|
||||||
|
},
|
||||||
|
|
||||||
|
deactivated() {
|
||||||
|
this.closeInterval()
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
initScroll() {
|
||||||
|
this.autoToBottom();
|
||||||
let scrollListener = typeof this.$listeners['on-scroll'] === "function";
|
let scrollListener = typeof this.$listeners['on-scroll'] === "function";
|
||||||
let scrollerView = $A(this.$refs.scrollerView);
|
let scrollerView = $A(this.$refs.scrollerView);
|
||||||
scrollerView.scroll(() => {
|
scrollerView.scroll(() => {
|
||||||
@ -72,16 +111,31 @@ export default {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
},
|
|
||||||
activated() {
|
recoveryScroll() {
|
||||||
if (this.scrollY > 0) {
|
if (this.autoRecovery && (this.scrollY > 0 || this.autoBottom)) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.scrollTo(this.scrollY);
|
if (this.autoBottom) {
|
||||||
});
|
this.autoToBottom();
|
||||||
}
|
} else {
|
||||||
},
|
this.scrollTo(this.scrollY, this.autoRecoveryAnimate);
|
||||||
methods: {
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
openInterval() {
|
||||||
|
this.autoToBottom();
|
||||||
|
this.autoInterval && clearInterval(this.autoInterval);
|
||||||
|
this.autoInterval = setInterval(this.autoToBottom, 300)
|
||||||
|
},
|
||||||
|
|
||||||
|
closeInterval() {
|
||||||
|
clearInterval(this.autoInterval);
|
||||||
|
this.autoInterval = null;
|
||||||
|
},
|
||||||
|
|
||||||
scrollTo(top, animate) {
|
scrollTo(top, animate) {
|
||||||
if (animate === false) {
|
if (animate === false) {
|
||||||
$A(this.$refs.scrollerView).stop().scrollTop(top);
|
$A(this.$refs.scrollerView).stop().scrollTop(top);
|
||||||
@ -89,10 +143,16 @@ export default {
|
|||||||
$A(this.$refs.scrollerView).stop().animate({"scrollTop": top});
|
$A(this.$refs.scrollerView).stop().animate({"scrollTop": top});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
scrollToBottom(animate) {
|
scrollToBottom(animate) {
|
||||||
this.scrollTo(this.$refs.scrollerView.scrollHeight, animate);
|
this.scrollTo(this.$refs.scrollerView.scrollHeight, animate);
|
||||||
},
|
},
|
||||||
getScrollInfo() {
|
|
||||||
|
autoToBottom() {
|
||||||
|
this.autoBottom && this.$refs.bottom.scrollIntoView(false);
|
||||||
|
},
|
||||||
|
|
||||||
|
scrollInfo() {
|
||||||
let scrollerView = $A(this.$refs.scrollerView);
|
let scrollerView = $A(this.$refs.scrollerView);
|
||||||
let wInnerH = Math.round(scrollerView.innerHeight());
|
let wInnerH = Math.round(scrollerView.innerHeight());
|
||||||
let wScrollY = scrollerView.scrollTop();
|
let wScrollY = scrollerView.scrollTop();
|
||||||
|
@ -9,8 +9,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="avatar-wrapper">
|
<div class="avatar-wrapper">
|
||||||
<div :class="['avatar-box', user.online ? 'online' : '']">
|
<div :class="['avatar-box', user.online ? 'online' : '']">
|
||||||
<Avatar v-if="showImg" :src="user.userimg" :size="size"/>
|
<WAvatar v-if="showImg" :src="user.userimg" :size="size"/>
|
||||||
<Avatar v-else :size="size" class="avatar-text">{{nickname}}</Avatar>
|
<WAvatar v-else :size="size" class="avatar-text">{{nickname}}</WAvatar>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="showName" class="avatar-name">{{user.nickname}}</div>
|
<div v-if="showName" class="avatar-name">{{user.nickname}}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -18,8 +18,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import WAvatar from "./WAvatar";
|
||||||
|
import {mapState} from "vuex";
|
||||||
export default {
|
export default {
|
||||||
name: 'UserAvatar',
|
name: 'UserAvatar',
|
||||||
|
components: {WAvatar},
|
||||||
props: {
|
props: {
|
||||||
userid: {
|
userid: {
|
||||||
type: [String, Number],
|
type: [String, Number],
|
||||||
@ -47,6 +50,8 @@
|
|||||||
this.getData()
|
this.getData()
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
...mapState(["userOnline"]),
|
||||||
|
|
||||||
showImg() {
|
showImg() {
|
||||||
const {userimg} = this.user
|
const {userimg} = this.user
|
||||||
if (!userimg) {
|
if (!userimg) {
|
||||||
@ -54,6 +59,7 @@
|
|||||||
}
|
}
|
||||||
return !$A.rightExists(userimg, '/avatar.png');
|
return !$A.rightExists(userimg, '/avatar.png');
|
||||||
},
|
},
|
||||||
|
|
||||||
nickname() {
|
nickname() {
|
||||||
const {nickname} = this.user;
|
const {nickname} = this.user;
|
||||||
if (!nickname) {
|
if (!nickname) {
|
||||||
@ -69,6 +75,12 @@
|
|||||||
watch: {
|
watch: {
|
||||||
userid() {
|
userid() {
|
||||||
this.getData()
|
this.getData()
|
||||||
|
},
|
||||||
|
|
||||||
|
userOnline(data) {
|
||||||
|
if (this.user && data[this.user.userid]) {
|
||||||
|
this.$set(this.user, 'online', data[this.user.userid]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<div v-if="multipleMax" slot="drop-prepend" class="user-drop-prepend">{{$L('最多只能选择' + multipleMax + '个')}}</div>
|
<div v-if="multipleMax" slot="drop-prepend" class="user-drop-prepend">{{$L('最多只能选择' + multipleMax + '个')}}</div>
|
||||||
<Option v-for="(item, key) in lists" :value="item.userid" :key="key" :label="item.nickname" :avatar="item.userimg">
|
<Option v-for="(item, key) in lists" :value="item.userid" :key="key" :label="item.nickname" :avatar="item.userimg">
|
||||||
<div class="user-input-option">
|
<div class="user-input-option">
|
||||||
<div class="user-input-avatar"><Avatar :src="item.userimg"/></div>
|
<div class="user-input-avatar"><WAvatar :src="item.userimg"/></div>
|
||||||
<div class="user-input-nickname">{{ item.nickname }}</div>
|
<div class="user-input-nickname">{{ item.nickname }}</div>
|
||||||
<div class="user-input-userid">ID: {{ item.userid }}</div>
|
<div class="user-input-userid">ID: {{ item.userid }}</div>
|
||||||
</div>
|
</div>
|
||||||
@ -30,8 +30,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import WAvatar from "./WAvatar";
|
||||||
export default {
|
export default {
|
||||||
name: 'UserInput',
|
name: 'UserInput',
|
||||||
|
components: {WAvatar},
|
||||||
props: {
|
props: {
|
||||||
value: {
|
value: {
|
||||||
type: [String, Number, Array],
|
type: [String, Number, Array],
|
||||||
|
124
resources/assets/js/components/WAvatar.vue
Normal file
124
resources/assets/js/components/WAvatar.vue
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<template>
|
||||||
|
<span :class="classes" :style="styles">
|
||||||
|
<img :src="src" v-if="src" @error="handleError">
|
||||||
|
<Icon :type="icon" :custom="customIcon" v-else-if="icon || customIcon"></Icon>
|
||||||
|
<span ref="children" :class="[prefixCls + '-string']" :style="childrenStyle" v-else><slot></slot></span>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import Icon from 'view-design-hi/src/components/icon';
|
||||||
|
import { oneOf } from 'view-design-hi/src/utils/assist';
|
||||||
|
|
||||||
|
const prefixCls = 'ivu-avatar';
|
||||||
|
|
||||||
|
const sizeList = ['small', 'large', 'default'];
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'WAvatar',
|
||||||
|
components: { Icon },
|
||||||
|
props: {
|
||||||
|
shape: {
|
||||||
|
validator (value) {
|
||||||
|
return oneOf(value, ['circle', 'square']);
|
||||||
|
},
|
||||||
|
default: 'circle'
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
type: [String, Number],
|
||||||
|
default () {
|
||||||
|
return !this.$IVIEW || this.$IVIEW.size === '' ? 'default' : this.$IVIEW.size;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
src: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
icon: {
|
||||||
|
type: String
|
||||||
|
},
|
||||||
|
customIcon: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
prefixCls: prefixCls,
|
||||||
|
scale: 1,
|
||||||
|
childrenWidth: 0,
|
||||||
|
isSlotShow: false,
|
||||||
|
slotTemp: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
classes () {
|
||||||
|
return [
|
||||||
|
`${prefixCls}`,
|
||||||
|
`${prefixCls}-${this.shape}`,
|
||||||
|
{
|
||||||
|
[`${prefixCls}-image`]: !!this.src,
|
||||||
|
[`${prefixCls}-icon`]: !!this.icon || !!this.customIcon,
|
||||||
|
[`${prefixCls}-${this.size}`]: oneOf(this.size, sizeList)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
},
|
||||||
|
styles () {
|
||||||
|
let style = {};
|
||||||
|
if (this.size && !oneOf(this.size, sizeList)) {
|
||||||
|
style.width = `${this.size}px`;
|
||||||
|
style.height = `${this.size}px`;
|
||||||
|
style.lineHeight = `${this.size}px`;
|
||||||
|
style.fontSize = `${this.size/2}px`;
|
||||||
|
}
|
||||||
|
return style;
|
||||||
|
},
|
||||||
|
childrenStyle () {
|
||||||
|
let style = {};
|
||||||
|
if (this.isSlotShow) {
|
||||||
|
style = {
|
||||||
|
msTransform: `scale(${this.scale})`,
|
||||||
|
WebkitTransform: `scale(${this.scale})`,
|
||||||
|
transform: `scale(${this.scale})`,
|
||||||
|
display: 'inline-block',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return style;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
size (val, oldVal) {
|
||||||
|
if (val !== oldVal) this.setScale();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setScale () {
|
||||||
|
this.isSlotShow = !this.src && !this.icon;
|
||||||
|
if (this.$refs.children) {
|
||||||
|
// set children width again to make slot centered
|
||||||
|
this.childrenWidth = this.$refs.children.offsetWidth;
|
||||||
|
const avatarWidth = this.$el.getBoundingClientRect().width;
|
||||||
|
// add 4px gap for each side to get better performance
|
||||||
|
if (avatarWidth - 8 < this.childrenWidth) {
|
||||||
|
this.scale = (avatarWidth - 8) / this.childrenWidth;
|
||||||
|
} else {
|
||||||
|
this.scale = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleError (e) {
|
||||||
|
this.$emit('on-error', e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
beforeCreate () {
|
||||||
|
this.slotTemp = this.$slots.default;
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.setScale();
|
||||||
|
},
|
||||||
|
updated () {
|
||||||
|
if (this.$slots.default !== this.slotTemp) {
|
||||||
|
this.slotTemp = this.$slots.default;
|
||||||
|
this.setScale();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
43
resources/assets/js/functions/web.js
vendored
43
resources/assets/js/functions/web.js
vendored
@ -19,10 +19,6 @@
|
|||||||
return window.location.origin + '/' + str;
|
return window.location.origin + '/' + str;
|
||||||
},
|
},
|
||||||
|
|
||||||
webUrl(str) {
|
|
||||||
return $A.fillUrl(str || '');
|
|
||||||
},
|
|
||||||
|
|
||||||
apiUrl(str) {
|
apiUrl(str) {
|
||||||
if (str.substring(0, 2) === "//" ||
|
if (str.substring(0, 2) === "//" ||
|
||||||
str.substring(0, 7) === "http://" ||
|
str.substring(0, 7) === "http://" ||
|
||||||
@ -34,6 +30,10 @@
|
|||||||
return apiUrl + str;
|
return apiUrl + str;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param params {url,data,method,timeout,header,spinner,websocket,timeout, before,complete,success,error,after}
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
apiAjax(params) {
|
apiAjax(params) {
|
||||||
if (!$A.isJson(params)) return false;
|
if (!$A.isJson(params)) return false;
|
||||||
if (typeof params.success === 'undefined') params.success = () => { };
|
if (typeof params.success === 'undefined') params.success = () => { };
|
||||||
@ -44,21 +44,23 @@
|
|||||||
params.header['language'] = $A.getLanguage();
|
params.header['language'] = $A.getLanguage();
|
||||||
params.header['token'] = $A.store.state.userToken;
|
params.header['token'] = $A.store.state.userToken;
|
||||||
//
|
//
|
||||||
let beforeCall = params.before;
|
if (params.spinner === true) {
|
||||||
params.before = () => {
|
let beforeCall = params.before;
|
||||||
$A.aAjaxLoadNum++;
|
params.before = () => {
|
||||||
$A(".common-spinner").show();
|
$A.aAjaxLoadNum++;
|
||||||
typeof beforeCall == "function" && beforeCall();
|
$A(".common-spinner").show();
|
||||||
};
|
typeof beforeCall == "function" && beforeCall();
|
||||||
//
|
};
|
||||||
let completeCall = params.complete;
|
//
|
||||||
params.complete = () => {
|
let completeCall = params.complete;
|
||||||
$A.aAjaxLoadNum--;
|
params.complete = () => {
|
||||||
if ($A.aAjaxLoadNum <= 0) {
|
$A.aAjaxLoadNum--;
|
||||||
$A(".common-spinner").hide();
|
if ($A.aAjaxLoadNum <= 0) {
|
||||||
}
|
$A(".common-spinner").hide();
|
||||||
typeof completeCall == "function" && completeCall();
|
}
|
||||||
};
|
typeof completeCall == "function" && completeCall();
|
||||||
|
};
|
||||||
|
}
|
||||||
//
|
//
|
||||||
let callback = params.success;
|
let callback = params.success;
|
||||||
params.success = (data, status, xhr) => {
|
params.success = (data, status, xhr) => {
|
||||||
@ -105,9 +107,9 @@
|
|||||||
});
|
});
|
||||||
//
|
//
|
||||||
params.complete = () => { };
|
params.complete = () => { };
|
||||||
params.after = () => { };
|
|
||||||
params.success = () => { };
|
params.success = () => { };
|
||||||
params.error = () => { };
|
params.error = () => { };
|
||||||
|
params.after = () => { };
|
||||||
params.header['Api-Websocket'] = apiWebsocket;
|
params.header['Api-Websocket'] = apiWebsocket;
|
||||||
//
|
//
|
||||||
if ($A.aAjaxWsReady === false) {
|
if ($A.aAjaxWsReady === false) {
|
||||||
@ -140,6 +142,7 @@
|
|||||||
}
|
}
|
||||||
//
|
//
|
||||||
$A.ajaxc(params);
|
$A.ajaxc(params);
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
aAjaxLoadNum: 0,
|
aAjaxLoadNum: 0,
|
||||||
aAjaxWsReady: false,
|
aAjaxWsReady: false,
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
<Icon type="ios-calendar-outline" />
|
<Icon type="ios-calendar-outline" />
|
||||||
<div class="menu-title">{{$L('日历')}}</div>
|
<div class="menu-title">{{$L('日历')}}</div>
|
||||||
</li>
|
</li>
|
||||||
<li @click="toggleRoute('dialog')" :class="classNameRoute('dialog')">
|
<li @click="toggleRoute('messenger')" :class="classNameRoute('messenger')">
|
||||||
<Icon type="ios-chatbubbles-outline" />
|
<Icon type="ios-chatbubbles-outline" />
|
||||||
<div class="menu-title">{{$L('消息')}}</div>
|
<div class="menu-title">{{$L('消息')}}</div>
|
||||||
</li>
|
</li>
|
||||||
|
@ -2,18 +2,18 @@
|
|||||||
<div class="dialog-view" :data-id="msgData.id">
|
<div class="dialog-view" :data-id="msgData.id">
|
||||||
|
|
||||||
<!--文本-->
|
<!--文本-->
|
||||||
<div v-if="msgData.type === 'text'" class="dialog-content" v-html="textMsg(msgInfo.text)"></div>
|
<div v-if="msgData.type === 'text'" class="dialog-content" v-html="textMsg(msgData.msg.text)"></div>
|
||||||
<!--等待-->
|
<!--等待-->
|
||||||
<div v-else-if="msgData.type === 'loading'" class="dialog-content loading"><Loading/></div>
|
<div v-else-if="msgData.type === 'loading'" class="dialog-content loading"><Loading/></div>
|
||||||
<!--文件-->
|
<!--文件-->
|
||||||
<div v-else-if="msgData.type === 'file'" :class="['dialog-content', msgInfo.type]">
|
<div v-else-if="msgData.type === 'file'" :class="['dialog-content', msgData.msg.type]">
|
||||||
<a :href="msgInfo.url" target="_blank">
|
<a :href="msgData.msg.url" target="_blank">
|
||||||
<img v-if="msgInfo.type === 'img'" class="file-img" :style="imageStyle(msgInfo)" :src="msgInfo.thumb"/>
|
<img v-if="msgData.msg.type === 'img'" class="file-img" :style="imageStyle(msgData.msg)" :src="msgData.msg.thumb"/>
|
||||||
<div v-else class="file-box">
|
<div v-else class="file-box">
|
||||||
<img class="file-thumb" :src="msgInfo.thumb"/>
|
<img class="file-thumb" :src="msgData.msg.thumb"/>
|
||||||
<div class="file-info">
|
<div class="file-info">
|
||||||
<div class="file-name">{{msgInfo.name}}</div>
|
<div class="file-name">{{msgData.msg.name}}</div>
|
||||||
<div class="file-size">{{$A.bytesToSize(msgInfo.size)}}</div>
|
<div class="file-size">{{$A.bytesToSize(msgData.msg.size)}}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@ -74,7 +74,6 @@ export default {
|
|||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
msgInfo: {},
|
|
||||||
read_list: []
|
read_list: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -117,8 +116,6 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
parsingData() {
|
parsingData() {
|
||||||
this.msgInfo = this.msgData.msg;
|
|
||||||
//
|
|
||||||
const {userid, r, id} = this.msgData;
|
const {userid, r, id} = this.msgData;
|
||||||
if (userid == this.userId) return;
|
if (userid == this.userId) return;
|
||||||
if ($A.isJson(r) && r.read_at) return;
|
if ($A.isJson(r) && r.read_at) return;
|
||||||
@ -153,22 +150,22 @@ export default {
|
|||||||
imageStyle(info) {
|
imageStyle(info) {
|
||||||
const {width, height} = info;
|
const {width, height} = info;
|
||||||
if (width && height) {
|
if (width && height) {
|
||||||
let maxWidth = 220,
|
let maxW = 220,
|
||||||
maxHeight = 220,
|
maxH = 220,
|
||||||
tempWidth = width,
|
tempW = width,
|
||||||
tempHeight = height;
|
tempH = height;
|
||||||
if (width > maxWidth || height > maxHeight) {
|
if (width > maxW || height > maxH) {
|
||||||
if (width > height) {
|
if (width > height) {
|
||||||
tempWidth = maxWidth;
|
tempW = maxW;
|
||||||
tempHeight = height * (maxWidth / width);
|
tempH = height * (maxW / width);
|
||||||
} else {
|
} else {
|
||||||
tempWidth = width * (maxHeight / height);
|
tempW = width * (maxH / height);
|
||||||
tempHeight = maxHeight;
|
tempH = maxH;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
width: tempWidth + 'px',
|
width: tempW + 'px',
|
||||||
height: tempHeight + 'px',
|
height: tempH + 'px',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
|
235
resources/assets/js/pages/manage/components/DialogWrapper.vue
Normal file
235
resources/assets/js/pages/manage/components/DialogWrapper.vue
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="dialog-wrapper"
|
||||||
|
@drop.prevent="chatPasteDrag($event, 'drag')"
|
||||||
|
@dragover.prevent="chatDragOver(true)"
|
||||||
|
@dragleave.prevent="chatDragOver(false)">
|
||||||
|
<slot name="head">
|
||||||
|
<div class="dialog-title">
|
||||||
|
<h2>{{dialogDetail.name}}</h2>
|
||||||
|
<em v-if="peopleNum > 0">({{peopleNum}})</em>
|
||||||
|
</div>
|
||||||
|
</slot>
|
||||||
|
<ScrollerY ref="scroller" class="dialog-chat dialog-scroller" :auto-bottom="autoBottom" @on-scroll="chatScroll">
|
||||||
|
<div ref="manageList" class="dialog-list">
|
||||||
|
<ul>
|
||||||
|
<li v-if="dialogMsgLoad > 0" class="loading"><Loading/></li>
|
||||||
|
<li v-else-if="dialogMsgList.length === 0" class="nothing">{{$L('暂无消息')}}</li>
|
||||||
|
<li v-for="(item, key) in dialogMsgList" :key="key" :class="{self:item.userid == userId}">
|
||||||
|
<div class="dialog-avatar">
|
||||||
|
<UserAvatar :userid="item.userid" :size="30"/>
|
||||||
|
</div>
|
||||||
|
<DialogView :msg-data="item" dialog-type="group"/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</ScrollerY>
|
||||||
|
<div :class="['dialog-footer', msgNew > 0 ? 'newmsg' : '']">
|
||||||
|
<div class="dialog-newmsg" @click="goNewBottom">{{$L('有' + msgNew + '条新消息')}}</div>
|
||||||
|
<DragInput class="dialog-input" v-model="msgText" type="textarea" :rows="1" :autosize="{ minRows: 1, maxRows: 3 }" :maxlength="255" @on-keydown="chatKeydown" @on-input-paste="pasteDrag" :placeholder="$L('输入消息...')" />
|
||||||
|
<DialogUpload
|
||||||
|
ref="chatUpload"
|
||||||
|
class="chat-upload"
|
||||||
|
@on-progress="chatFile('progress', $event)"
|
||||||
|
@on-success="chatFile('success', $event)"
|
||||||
|
@on-error="chatFile('error', $event)"/>
|
||||||
|
</div>
|
||||||
|
<div v-if="dialogDrag" class="drag-over" @click="dialogDrag=false">
|
||||||
|
<div class="drag-text">{{$L('拖动到这里发送')}}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import DragInput from "../../../components/DragInput";
|
||||||
|
import ScrollerY from "../../../components/ScrollerY";
|
||||||
|
import {mapState} from "vuex";
|
||||||
|
import DialogView from "./DialogView";
|
||||||
|
import DialogUpload from "./DialogUpload";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "DialogWrapper",
|
||||||
|
components: {DialogUpload, DialogView, ScrollerY, DragInput},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
autoBottom: true,
|
||||||
|
autoInterval: null,
|
||||||
|
|
||||||
|
memberShowAll: false,
|
||||||
|
|
||||||
|
dialogDrag: false,
|
||||||
|
|
||||||
|
msgText: '',
|
||||||
|
msgLength: 0,
|
||||||
|
msgNew: 0,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
...mapState(['userId', 'dialogId', 'dialogDetail', 'dialogMsgLoad', 'dialogMsgList']),
|
||||||
|
|
||||||
|
peopleNum() {
|
||||||
|
return this.dialogDetail.type === 'group' ? $A.runNum(this.dialogDetail.people) : 0;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
watch: {
|
||||||
|
dialogMsgList(list) {
|
||||||
|
if (!this.autoBottom) {
|
||||||
|
let length = list.length - this.msgLength;
|
||||||
|
if (length > 0) {
|
||||||
|
this.msgNew+= length;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.$nextTick(this.goBottom);
|
||||||
|
}
|
||||||
|
this.msgLength = list.length;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
sendMsg() {
|
||||||
|
let tempId = $A.randomString(16);
|
||||||
|
this.dialogMsgList.push({
|
||||||
|
id: tempId,
|
||||||
|
type: 'text',
|
||||||
|
userid: this.userId,
|
||||||
|
msg: {
|
||||||
|
text: this.msgText,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
this.goBottom();
|
||||||
|
//
|
||||||
|
$A.apiAjax({
|
||||||
|
url: 'dialog/msg/sendtext',
|
||||||
|
data: {
|
||||||
|
dialog_id: this.dialogId,
|
||||||
|
text: this.msgText,
|
||||||
|
},
|
||||||
|
error:() => {
|
||||||
|
this.$store.commit('spliceDialogMsg', {id: tempId});
|
||||||
|
},
|
||||||
|
success: ({ret, data, msg}) => {
|
||||||
|
if (ret !== 1) {
|
||||||
|
$A.modalWarning({
|
||||||
|
title: '发送失败',
|
||||||
|
content: msg
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.$store.commit('spliceDialogMsg', {
|
||||||
|
id: tempId,
|
||||||
|
data: ret === 1 ? data : null
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
//
|
||||||
|
this.msgText = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
chatKeydown(e) {
|
||||||
|
if (e.keyCode === 13) {
|
||||||
|
if (e.shiftKey) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
this.sendMsg();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
pasteDrag(e, type) {
|
||||||
|
const files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
|
||||||
|
const postFiles = Array.prototype.slice.call(files);
|
||||||
|
if (postFiles.length > 0) {
|
||||||
|
e.preventDefault();
|
||||||
|
postFiles.forEach((file) => {
|
||||||
|
this.$refs.chatUpload.upload(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
chatDragOver(show) {
|
||||||
|
let random = (this.__dialogDrag = $A.randomString(8));
|
||||||
|
if (!show) {
|
||||||
|
setTimeout(() => {
|
||||||
|
if (random === this.__dialogDrag) {
|
||||||
|
this.dialogDrag = show;
|
||||||
|
}
|
||||||
|
}, 150);
|
||||||
|
} else {
|
||||||
|
this.dialogDrag = show;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
chatPasteDrag(e, type) {
|
||||||
|
this.dialogDrag = false;
|
||||||
|
const files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
|
||||||
|
const postFiles = Array.prototype.slice.call(files);
|
||||||
|
if (postFiles.length > 0) {
|
||||||
|
e.preventDefault();
|
||||||
|
postFiles.forEach((file) => {
|
||||||
|
this.$refs.chatUpload.upload(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
chatFile(type, file) {
|
||||||
|
switch (type) {
|
||||||
|
case 'progress':
|
||||||
|
this.dialogMsgList.push({
|
||||||
|
id: file.tempId,
|
||||||
|
type: 'loading',
|
||||||
|
userid: this.userId,
|
||||||
|
msg: { },
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'error':
|
||||||
|
this.$store.commit('spliceDialogMsg', {id: file.tempId});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'success':
|
||||||
|
this.$store.commit('spliceDialogMsg', {id: file.tempId, data: file.data});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
chatScroll(res) {
|
||||||
|
switch (res.directionreal) {
|
||||||
|
case 'up':
|
||||||
|
if (res.scrollE < 10) {
|
||||||
|
this.autoBottom = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'down':
|
||||||
|
this.autoBottom = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
goBottom() {
|
||||||
|
if (this.autoBottom) {
|
||||||
|
this.$refs.scroller.autoToBottom();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
goNewBottom() {
|
||||||
|
this.msgNew = 0;
|
||||||
|
this.autoBottom = true;
|
||||||
|
this.goBottom();
|
||||||
|
},
|
||||||
|
|
||||||
|
formatTime(date) {
|
||||||
|
let time = Math.round(new Date(date).getTime() / 1000),
|
||||||
|
string = '';
|
||||||
|
if ($A.formatDate('Ymd') === $A.formatDate('Ymd', time)) {
|
||||||
|
string = $A.formatDate('H:i', time)
|
||||||
|
} else if ($A.formatDate('Y') === $A.formatDate('Y', time)) {
|
||||||
|
string = $A.formatDate('m-d', time)
|
||||||
|
} else {
|
||||||
|
string = $A.formatDate('Y-m-d', time)
|
||||||
|
}
|
||||||
|
return string || '';
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
@ -1,74 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div v-if="$store.state.projectChatShow" class="project-dialog">
|
||||||
v-if="$store.state.projectChatShow"
|
<DialogWrapper class="project-dialog-wrapper">
|
||||||
class="project-dialog"
|
<div slot="head">
|
||||||
@drop.prevent="chatPasteDrag($event, 'drag')"
|
<div class="dialog-user">
|
||||||
@dragover.prevent="chatDragOver(true)"
|
<div class="member-head">
|
||||||
@dragleave.prevent="chatDragOver(false)">
|
<div class="member-title">{{$L('项目成员')}}<span>({{projectDetail.project_user.length}})</span></div>
|
||||||
<div class="dialog-user">
|
<div class="member-view-all" @click="memberShowAll=!memberShowAll">{{$L('查看所有')}}</div>
|
||||||
<div class="member-head">
|
</div>
|
||||||
<div class="member-title">{{$L('项目成员')}}<span>({{projectDetail.project_user.length}})</span></div>
|
<ul :class="['member-list', memberShowAll ? 'member-all' : '']">
|
||||||
<div class="member-view-all" @click="memberShowAll=!memberShowAll">{{$L('查看所有')}}</div>
|
<li v-for="item in projectDetail.project_user">
|
||||||
|
<UserAvatar :userid="item.userid" :size="36"/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-title">
|
||||||
|
<h2>{{$L('群聊')}}</h2>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul :class="['member-list', memberShowAll ? 'member-all' : '']">
|
</DialogWrapper>
|
||||||
<li v-for="item in projectDetail.project_user">
|
|
||||||
<UserAvatar :userid="item.userid" :size="36"/>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="dialog-title">{{$L('群聊')}}</div>
|
|
||||||
<ScrollerY class="dialog-chat dialog-scroller" @on-scroll="chatScroll">
|
|
||||||
<div ref="manageList" class="dialog-list">
|
|
||||||
<ul>
|
|
||||||
<li v-if="dialogMsgLoad > 0" class="loading"><Loading/></li>
|
|
||||||
<li v-else-if="dialogMsgList.length === 0" class="nothing">{{$L('暂无消息')}}</li>
|
|
||||||
<li v-for="(item, key) in dialogMsgList" :key="key" :class="{self:item.userid == userId}">
|
|
||||||
<div class="dialog-avatar">
|
|
||||||
<UserAvatar :userid="item.userid" :size="30"/>
|
|
||||||
</div>
|
|
||||||
<DialogView :msg-data="item" dialog-type="group"/>
|
|
||||||
</li>
|
|
||||||
<li ref="bottom" class="bottom"></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</ScrollerY>
|
|
||||||
<div :class="['dialog-footer', msgNew > 0 ? 'newmsg' : '']">
|
|
||||||
<div class="dialog-newmsg" @click="goNewBottom">{{$L('有' + msgNew + '条新消息')}}</div>
|
|
||||||
<DragInput class="dialog-input" v-model="msgText" type="textarea" :rows="1" :autosize="{ minRows: 1, maxRows: 3 }" :maxlength="255" @on-keydown="chatKeydown" @on-input-paste="pasteDrag" :placeholder="$L('输入消息...')" />
|
|
||||||
<DialogUpload
|
|
||||||
ref="chatUpload"
|
|
||||||
class="chat-upload"
|
|
||||||
@on-progress="chatFile('progress', $event)"
|
|
||||||
@on-success="chatFile('success', $event)"
|
|
||||||
@on-error="chatFile('error', $event)"/>
|
|
||||||
</div>
|
|
||||||
<div v-if="dialogDrag" class="drag-over" @click="dialogDrag=false">
|
|
||||||
<div class="drag-text">{{$L('拖动到这里发送')}}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
:global {
|
|
||||||
.project-dialog {
|
|
||||||
.dialog-footer {
|
|
||||||
.dialog-input {
|
|
||||||
background-color: #F4F5F7;
|
|
||||||
padding: 10px 12px;
|
|
||||||
border-radius: 10px;
|
|
||||||
.ivu-input {
|
|
||||||
border: 0;
|
|
||||||
resize: none;
|
|
||||||
background-color: transparent;
|
|
||||||
&:focus {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
:global {
|
:global {
|
||||||
.project-dialog {
|
.project-dialog {
|
||||||
@ -76,118 +28,51 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
.dialog-user {
|
.project-dialog-wrapper {
|
||||||
margin-top: 36px;
|
|
||||||
padding: 0 32px;
|
|
||||||
.member-head {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
.member-title {
|
|
||||||
flex: 1;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
> span {
|
|
||||||
padding-left: 6px;
|
|
||||||
color: #2d8cf0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.member-view-all {
|
|
||||||
color: #999;
|
|
||||||
font-size: 13px;
|
|
||||||
cursor: pointer;
|
|
||||||
&:hover {
|
|
||||||
color: #777;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.member-list {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-top: 14px;
|
|
||||||
overflow: auto;
|
|
||||||
> li {
|
|
||||||
position: relative;
|
|
||||||
list-style: none;
|
|
||||||
margin-right: 14px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
&.member-all {
|
|
||||||
display: block;
|
|
||||||
> li {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.dialog-title {
|
|
||||||
padding: 0 32px;
|
|
||||||
margin-top: 20px;
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.dialog-chat {
|
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 0 32px;
|
height: 0;
|
||||||
margin-top: 18px;
|
.dialog-user {
|
||||||
}
|
margin-top: 36px;
|
||||||
.dialog-footer {
|
padding: 0 32px;
|
||||||
display: flex;
|
.member-head {
|
||||||
flex-direction: column;
|
display: flex;
|
||||||
align-items: flex-end;
|
align-items: center;
|
||||||
padding: 0 28px;
|
.member-title {
|
||||||
margin-bottom: 20px;
|
flex: 1;
|
||||||
.dialog-newmsg {
|
font-size: 18px;
|
||||||
display: none;
|
font-weight: 600;
|
||||||
height: 30px;
|
> span {
|
||||||
line-height: 30px;
|
padding-left: 6px;
|
||||||
color: #ffffff;
|
color: #2d8cf0;
|
||||||
font-size: 12px;
|
}
|
||||||
background-color: rgba(0, 0, 0, 0.6);
|
}
|
||||||
padding: 0 12px;
|
.member-view-all {
|
||||||
margin-bottom: 20px;
|
color: #999;
|
||||||
margin-right: 10px;
|
font-size: 13px;
|
||||||
border-radius: 16px;
|
cursor: pointer;
|
||||||
cursor: pointer;
|
&:hover {
|
||||||
z-index: 2;;
|
color: #777;
|
||||||
}
|
}
|
||||||
.chat-upload {
|
}
|
||||||
display: none;
|
}
|
||||||
width: 0;
|
.member-list {
|
||||||
height: 0;
|
display: flex;
|
||||||
overflow: hidden;
|
align-items: center;
|
||||||
}
|
margin-top: 14px;
|
||||||
&.newmsg {
|
overflow: auto;
|
||||||
margin-top: -50px;
|
> li {
|
||||||
.dialog-newmsg {
|
position: relative;
|
||||||
display: block;
|
list-style: none;
|
||||||
|
margin-right: 14px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
&.member-all {
|
||||||
|
display: block;
|
||||||
|
> li {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
.drag-over {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
z-index: 3;
|
|
||||||
background-color: rgba(255, 255, 255, 0.78);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
&:before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
top: 16px;
|
|
||||||
left: 16px;
|
|
||||||
right: 16px;
|
|
||||||
bottom: 16px;
|
|
||||||
border: 2px dashed #7b7b7b;
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
.drag-text {
|
|
||||||
padding: 12px;
|
|
||||||
font-size: 18px;
|
|
||||||
color: #666666;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -195,207 +80,37 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import DragInput from "../../../components/DragInput";
|
|
||||||
import ScrollerY from "../../../components/ScrollerY";
|
|
||||||
import {mapState} from "vuex";
|
import {mapState} from "vuex";
|
||||||
import DialogView from "./DialogView";
|
import DialogWrapper from "./DialogWrapper";
|
||||||
import DialogUpload from "./DialogUpload";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "ProjectDialog",
|
name: "ProjectDialog",
|
||||||
components: {DialogUpload, DialogView, ScrollerY, DragInput},
|
components: {DialogWrapper},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
autoBottom: true,
|
|
||||||
autoInterval: null,
|
|
||||||
|
|
||||||
memberShowAll: false,
|
memberShowAll: false,
|
||||||
|
|
||||||
dialogId: 0,
|
|
||||||
dialogDrag: false,
|
|
||||||
|
|
||||||
msgText: '',
|
|
||||||
msgLength: 0,
|
|
||||||
msgNew: 0,
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.goBottom();
|
|
||||||
this.autoInterval = setInterval(this.goBottom, 200)
|
|
||||||
},
|
|
||||||
|
|
||||||
beforeDestroy() {
|
|
||||||
clearInterval(this.autoInterval)
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['userId', 'projectDetail', 'projectMsgUnread', 'dialogMsgLoad', 'dialogMsgList']),
|
...mapState(['projectDetail', 'projectChatShow']),
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
projectDetail(detail) {
|
projectDetail() {
|
||||||
this.dialogId = detail.dialog_id;
|
this.getDialogMsg()
|
||||||
},
|
},
|
||||||
|
projectChatShow() {
|
||||||
dialogId(id) {
|
this.getDialogMsg()
|
||||||
this.$store.commit('getDialogMsg', id);
|
|
||||||
},
|
|
||||||
|
|
||||||
dialogMsgList(list) {
|
|
||||||
if (!this.autoBottom) {
|
|
||||||
let length = list.length - this.msgLength;
|
|
||||||
if (length > 0) {
|
|
||||||
this.msgNew+= length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.msgLength = list.length;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
sendMsg() {
|
getDialogMsg() {
|
||||||
let tempId = $A.randomString(16);
|
if (this.projectChatShow && this.projectDetail.dialog_id) {
|
||||||
this.dialogMsgList.push({
|
this.$store.commit('getDialogMsg', this.projectDetail.dialog_id);
|
||||||
id: tempId,
|
|
||||||
type: 'text',
|
|
||||||
userid: this.userId,
|
|
||||||
msg: {
|
|
||||||
text: this.msgText,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.goBottom();
|
|
||||||
//
|
|
||||||
$A.apiAjax({
|
|
||||||
url: 'dialog/msg/sendtext',
|
|
||||||
data: {
|
|
||||||
dialog_id: this.projectDetail.dialog_id,
|
|
||||||
text: this.msgText,
|
|
||||||
},
|
|
||||||
error:() => {
|
|
||||||
this.$store.commit('spliceDialogMsg', {id: tempId});
|
|
||||||
},
|
|
||||||
success: ({ret, data, msg}) => {
|
|
||||||
if (ret !== 1) {
|
|
||||||
$A.modalWarning({
|
|
||||||
title: '发送失败',
|
|
||||||
content: msg
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.$store.commit('spliceDialogMsg', {
|
|
||||||
id: tempId,
|
|
||||||
data: ret === 1 ? data : null
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
//
|
|
||||||
this.msgText = '';
|
|
||||||
},
|
|
||||||
|
|
||||||
chatKeydown(e) {
|
|
||||||
if (e.keyCode === 13) {
|
|
||||||
if (e.shiftKey) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
this.sendMsg();
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
pasteDrag(e, type) {
|
|
||||||
const files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
|
|
||||||
const postFiles = Array.prototype.slice.call(files);
|
|
||||||
if (postFiles.length > 0) {
|
|
||||||
e.preventDefault();
|
|
||||||
postFiles.forEach((file) => {
|
|
||||||
this.$refs.chatUpload.upload(file);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
chatDragOver(show) {
|
|
||||||
let random = (this.__dialogDrag = $A.randomString(8));
|
|
||||||
if (!show) {
|
|
||||||
setTimeout(() => {
|
|
||||||
if (random === this.__dialogDrag) {
|
|
||||||
this.dialogDrag = show;
|
|
||||||
}
|
|
||||||
}, 150);
|
|
||||||
} else {
|
|
||||||
this.dialogDrag = show;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
chatPasteDrag(e, type) {
|
|
||||||
this.dialogDrag = false;
|
|
||||||
const files = type === 'drag' ? e.dataTransfer.files : e.clipboardData.files;
|
|
||||||
const postFiles = Array.prototype.slice.call(files);
|
|
||||||
if (postFiles.length > 0) {
|
|
||||||
e.preventDefault();
|
|
||||||
postFiles.forEach((file) => {
|
|
||||||
this.$refs.chatUpload.upload(file);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
chatFile(type, file) {
|
|
||||||
switch (type) {
|
|
||||||
case 'progress':
|
|
||||||
this.dialogMsgList.push({
|
|
||||||
id: file.tempId,
|
|
||||||
type: 'loading',
|
|
||||||
userid: this.userId,
|
|
||||||
msg: { },
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'error':
|
|
||||||
this.$store.commit('spliceDialogMsg', {id: file.tempId});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'success':
|
|
||||||
this.$store.commit('spliceDialogMsg', {id: file.tempId, data: file.data});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
chatScroll(res) {
|
|
||||||
switch (res.directionreal) {
|
|
||||||
case 'up':
|
|
||||||
if (res.scrollE < 10) {
|
|
||||||
this.autoBottom = true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'down':
|
|
||||||
this.autoBottom = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
goBottom() {
|
|
||||||
if (this.autoBottom && this.$refs.bottom) {
|
|
||||||
this.$refs.bottom.scrollIntoView(false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
goNewBottom() {
|
|
||||||
this.msgNew = 0;
|
|
||||||
this.autoBottom = true;
|
|
||||||
this.goBottom();
|
|
||||||
},
|
|
||||||
|
|
||||||
formatTime(date) {
|
|
||||||
let time = Math.round(new Date(date).getTime() / 1000),
|
|
||||||
string = '';
|
|
||||||
if ($A.formatDate('Ymd') === $A.formatDate('Ymd', time)) {
|
|
||||||
string = $A.formatDate('H:i', time)
|
|
||||||
} else if ($A.formatDate('Y') === $A.formatDate('Y', time)) {
|
|
||||||
string = $A.formatDate('m-d', time)
|
|
||||||
} else {
|
|
||||||
string = $A.formatDate('Y-m-d', time)
|
|
||||||
}
|
|
||||||
return string || '';
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,35 +1,43 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dialog">
|
<div class="messenger">
|
||||||
<PageTitle>{{ $L('消息') }}</PageTitle>
|
<PageTitle>{{ $L('消息') }}</PageTitle>
|
||||||
<div class="dialog-wrapper">
|
<div class="messenger-wrapper">
|
||||||
|
|
||||||
<div class="dialog-select">
|
<div class="messenger-select">
|
||||||
<div class="dialog-search">
|
<div class="messenger-search">
|
||||||
<Input prefix="ios-search" v-model="dialogKey" :placeholder="$L('搜索...')" clearable />
|
<div class="search-wrapper">
|
||||||
|
<Input prefix="ios-search" v-model="dialogKey" :placeholder="$L('搜索...')" clearable />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-list overlay-y">
|
<div class="messenger-list overlay-y">
|
||||||
<ul>
|
<ul>
|
||||||
<li v-for="(dialog, key) in dialogLists" :key="key" :class="{active: dialog.id == dialogId}">
|
<li
|
||||||
|
v-for="(dialog, key) in dialogLists"
|
||||||
|
:key="key"
|
||||||
|
:class="{active: dialog.id == dialogId}"
|
||||||
|
@click="openDialog(dialog)">
|
||||||
<Icon v-if="dialog.type=='group'" class="group-avatar" type="ios-people" />
|
<Icon v-if="dialog.type=='group'" class="group-avatar" type="ios-people" />
|
||||||
<UserAvatar v-else-if="dialog.dialog_user" :userid="dialog.dialog_user.userid" :size="46"/>
|
<UserAvatar v-else-if="dialog.dialog_user" :userid="dialog.dialog_user.userid" :size="46"/>
|
||||||
<div class="user-msg-box">
|
<div class="dialog-box">
|
||||||
<div class="user-msg-title">
|
<div class="dialog-title">
|
||||||
<span>{{dialog.name}}</span>
|
<span>{{dialog.name}}</span>
|
||||||
<em v-if="dialog.last_at">{{formatTime(dialog.last_at)}}</em>
|
<em v-if="dialog.last_at">{{formatTime(dialog.last_at)}}</em>
|
||||||
</div>
|
</div>
|
||||||
<div class="user-msg-text">{{formatLastMsg(dialog.last_msg)}}</div>
|
<div class="dialog-text">{{formatLastMsg(dialog.last_msg)}}</div>
|
||||||
</div>
|
</div>
|
||||||
<Badge class="user-msg-num" :count="dialog.unread"/>
|
<Badge class="dialog-num" :count="dialog.unread"/>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-menu">
|
<div class="messenger-menu">
|
||||||
<Icon class="active" type="ios-chatbubbles" />
|
<Icon class="active" type="ios-chatbubbles" />
|
||||||
<Icon type="md-person" />
|
<Icon type="md-person" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dialog-msg"></div>
|
<div class="messenger-msg">
|
||||||
|
<DialogWrapper v-if="dialogId > 0"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -37,7 +45,7 @@
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
:global {
|
:global {
|
||||||
.dialog {
|
.messenger {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -45,8 +53,10 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {mapState} from "vuex";
|
import {mapState} from "vuex";
|
||||||
|
import DialogWrapper from "./components/DialogWrapper";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: {DialogWrapper},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
dialogLoad: 0,
|
dialogLoad: 0,
|
||||||
@ -67,8 +77,14 @@ export default {
|
|||||||
if (dialogKey == '') {
|
if (dialogKey == '') {
|
||||||
return this.dialogList;
|
return this.dialogList;
|
||||||
}
|
}
|
||||||
return this.dialogList.filter(({name}) => {
|
return this.dialogList.filter(({name, last_msg}) => {
|
||||||
return $A.strExists(name, dialogKey);
|
if ($A.strExists(name, dialogKey)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (last_msg && last_msg.type === 'text' && $A.strExists(last_msg.msg.text, dialogKey)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -107,6 +123,10 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
openDialog(dialog) {
|
||||||
|
this.$store.commit('getDialogMsg', dialog.id);
|
||||||
|
},
|
||||||
|
|
||||||
getDialogLists() {
|
getDialogLists() {
|
||||||
this.dialogLoad++;
|
this.dialogLoad++;
|
||||||
$A.apiAjax({
|
$A.apiAjax({
|
6
resources/assets/js/routes.js
vendored
6
resources/assets/js/routes.js
vendored
@ -23,9 +23,9 @@ export default [
|
|||||||
component: () => import('./pages/manage/calendar.vue'),
|
component: () => import('./pages/manage/calendar.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'manage-dialog',
|
name: 'manage-messenger',
|
||||||
path: 'dialog',
|
path: 'messenger',
|
||||||
component: () => import('./pages/manage/dialog.vue'),
|
component: () => import('./pages/manage/messenger.vue'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'manage-setting',
|
name: 'manage-setting',
|
||||||
|
42
resources/assets/js/store/mutations.js
vendored
42
resources/assets/js/store/mutations.js
vendored
@ -66,6 +66,18 @@ export default {
|
|||||||
this.commit('wsConnection');
|
this.commit('wsConnection');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新会员在线
|
||||||
|
* @param state
|
||||||
|
* @param info
|
||||||
|
*/
|
||||||
|
setUserOnlineStatus(state, info) {
|
||||||
|
const {userid, online} = info;
|
||||||
|
if (state.userOnline[userid] !== online) {
|
||||||
|
state.userOnline = Object.assign({}, state.userOnline, {[userid]: online});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取项目列表
|
* 获取项目列表
|
||||||
* @param state
|
* @param state
|
||||||
@ -101,6 +113,9 @@ export default {
|
|||||||
if (state.method.runNum(project_id) === 0) {
|
if (state.method.runNum(project_id) === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (state.projectDetail.id === project_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (state.method.isJson(state.cacheProject[project_id])) {
|
if (state.method.isJson(state.cacheProject[project_id])) {
|
||||||
state.projectDetail = state.cacheProject[project_id];
|
state.projectDetail = state.cacheProject[project_id];
|
||||||
}
|
}
|
||||||
@ -183,6 +198,7 @@ export default {
|
|||||||
time,
|
time,
|
||||||
data: item
|
data: item
|
||||||
};
|
};
|
||||||
|
this.commit('setUserOnlineStatus', item);
|
||||||
typeof success === "function" && success(item, true)
|
typeof success === "function" && success(item, true)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -201,10 +217,20 @@ export default {
|
|||||||
if (state.method.runNum(dialog_id) === 0) {
|
if (state.method.runNum(dialog_id) === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (state.method.isArray(state.cacheDialog[dialog_id])) {
|
if (state.dialogId === dialog_id) {
|
||||||
state.dialogMsgList = state.cacheDialog[dialog_id]
|
return;
|
||||||
} else {
|
}
|
||||||
state.dialogMsgList = [];
|
//
|
||||||
|
state.dialogMsgList = [];
|
||||||
|
if (state.method.isJson(state.cacheDialog[dialog_id])) {
|
||||||
|
setTimeout(() => {
|
||||||
|
let length = state.cacheDialog[dialog_id].data.length;
|
||||||
|
if (length > 50) {
|
||||||
|
state.cacheDialog[dialog_id].data.splice(0, length - 50);
|
||||||
|
}
|
||||||
|
state.dialogDetail = state.cacheDialog[dialog_id].dialog
|
||||||
|
state.dialogMsgList = state.cacheDialog[dialog_id].data
|
||||||
|
});
|
||||||
}
|
}
|
||||||
state.dialogId = dialog_id;
|
state.dialogId = dialog_id;
|
||||||
//
|
//
|
||||||
@ -225,9 +251,13 @@ export default {
|
|||||||
},
|
},
|
||||||
success: ({ret, data, msg}) => {
|
success: ({ret, data, msg}) => {
|
||||||
if (ret === 1) {
|
if (ret === 1) {
|
||||||
state.cacheDialog[dialog_id] = data.data.reverse();
|
state.cacheDialog[dialog_id] = {
|
||||||
|
dialog: data.dialog,
|
||||||
|
data: data.data.reverse(),
|
||||||
|
};
|
||||||
if (state.dialogId === dialog_id) {
|
if (state.dialogId === dialog_id) {
|
||||||
state.cacheDialog[dialog_id].forEach((item) => {
|
state.dialogDetail = state.cacheDialog[dialog_id].dialog;
|
||||||
|
state.cacheDialog[dialog_id].data.forEach((item) => {
|
||||||
let index = state.dialogMsgList.findIndex(({id}) => id === item.id);
|
let index = state.dialogMsgList.findIndex(({id}) => id === item.id);
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
state.dialogMsgList.push(item);
|
state.dialogMsgList.push(item);
|
||||||
|
3
resources/assets/js/store/state.js
vendored
3
resources/assets/js/store/state.js
vendored
@ -164,6 +164,7 @@ state.userInfo = state.method.getStorageJson('userInfo');
|
|||||||
state.userId = state.userInfo.userid = state.method.runNum(state.userInfo.userid);
|
state.userId = state.userInfo.userid = state.method.runNum(state.userInfo.userid);
|
||||||
state.userToken = state.userInfo.token;
|
state.userToken = state.userInfo.token;
|
||||||
state.userIsAdmin = state.method.inArray('admin', state.userInfo.identity);
|
state.userIsAdmin = state.method.inArray('admin', state.userInfo.identity);
|
||||||
|
state.userOnline = {};
|
||||||
|
|
||||||
// Websocket
|
// Websocket
|
||||||
state.ws = null;
|
state.ws = null;
|
||||||
@ -177,6 +178,7 @@ state.projectLoad = 0;
|
|||||||
state.projectList = [];
|
state.projectList = [];
|
||||||
state.projectDetail = {
|
state.projectDetail = {
|
||||||
id: 0,
|
id: 0,
|
||||||
|
dialog_id: 0,
|
||||||
project_column: [],
|
project_column: [],
|
||||||
project_user: []
|
project_user: []
|
||||||
};
|
};
|
||||||
@ -184,6 +186,7 @@ state.projectMsgUnread = 0;
|
|||||||
|
|
||||||
// 会话消息
|
// 会话消息
|
||||||
state.dialogId = 0;
|
state.dialogId = 0;
|
||||||
|
state.dialogDetail = {};
|
||||||
state.dialogMsgLoad = 0;
|
state.dialogMsgLoad = 0;
|
||||||
state.dialogMsgList = [];
|
state.dialogMsgList = [];
|
||||||
|
|
||||||
|
146
resources/assets/sass/main.scss
vendored
146
resources/assets/sass/main.scss
vendored
@ -701,6 +701,8 @@ body {
|
|||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
}
|
}
|
||||||
.dialog-view {
|
.dialog-view {
|
||||||
max-width: 70%;
|
max-width: 70%;
|
||||||
@ -899,10 +901,111 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dialog-wrapper {
|
.dialog-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
background-color: #ffffff;
|
||||||
|
z-index: 1;
|
||||||
|
.dialog-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 32px;
|
||||||
|
margin-top: 20px;
|
||||||
|
> h2 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
> em {
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 500;
|
||||||
|
padding-left: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.dialog-chat {
|
||||||
|
flex: 1;
|
||||||
|
padding: 0 32px;
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-end;
|
||||||
|
padding: 0 28px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
.dialog-newmsg {
|
||||||
|
display: none;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 12px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
padding: 0 12px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
margin-right: 10px;
|
||||||
|
border-radius: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 2;;
|
||||||
|
}
|
||||||
|
.dialog-input {
|
||||||
|
background-color: #F4F5F7;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
.ivu-input {
|
||||||
|
border: 0;
|
||||||
|
resize: none;
|
||||||
|
background-color: transparent;
|
||||||
|
&:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.chat-upload {
|
||||||
|
display: none;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
&.newmsg {
|
||||||
|
margin-top: -50px;
|
||||||
|
.dialog-newmsg {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.drag-over {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 3;
|
||||||
|
background-color: rgba(255, 255, 255, 0.78);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
&:before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 16px;
|
||||||
|
left: 16px;
|
||||||
|
right: 16px;
|
||||||
|
bottom: 16px;
|
||||||
|
border: 2px dashed #7b7b7b;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
.drag-text {
|
||||||
|
padding: 12px;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #666666;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.messenger-wrapper {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
.dialog-select {
|
.messenger-select {
|
||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 30%;
|
width: 30%;
|
||||||
@ -920,22 +1023,31 @@ body {
|
|||||||
width: 1px;
|
width: 1px;
|
||||||
background-color: #f2f2f2;
|
background-color: #f2f2f2;
|
||||||
}
|
}
|
||||||
.dialog-search {
|
.messenger-search {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
height: 54px;
|
height: 54px;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
.ivu-input {
|
.search-wrapper {
|
||||||
border-color: transparent;
|
flex: 1;
|
||||||
&:hover,
|
background-color: #F7F7F7;
|
||||||
&:focus {
|
padding: 0 8px;
|
||||||
box-shadow: none;
|
margin: 0 4px;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
.ivu-input {
|
||||||
|
border-color: transparent;
|
||||||
|
background-color: transparent;
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.dialog-list {
|
.messenger-list {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 0;
|
height: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -946,7 +1058,7 @@ body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 86px;
|
height: 80px;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
position: relative;
|
position: relative;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -968,12 +1080,12 @@ body {
|
|||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.user-msg-box {
|
.dialog-box {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding-left: 12px;
|
padding-left: 12px;
|
||||||
.user-msg-title {
|
.dialog-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -995,7 +1107,7 @@ body {
|
|||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.user-msg-text {
|
.dialog-text {
|
||||||
max-width: 170px;
|
max-width: 170px;
|
||||||
color: #999999;
|
color: #999999;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
@ -1005,7 +1117,7 @@ body {
|
|||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.user-msg-num {
|
.dialog-num {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 12px;
|
top: 12px;
|
||||||
left: 38px;
|
left: 38px;
|
||||||
@ -1020,7 +1132,7 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.dialog-menu {
|
.messenger-menu {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -1043,9 +1155,13 @@ body {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.dialog-msg {
|
.messenger-msg {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
width: 0;
|
width: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
.dialog-wrapper {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user