u
This commit is contained in:
1 parent
c26d1b3c81
commit
fac3206497
24 files changed
+364
-284
No files matched your search
+7
-1
@@ -7,9 +7,9 @@ import cn.link56.company.model.dto.DeleteCompanyUserDTO;
|
||||
import cn.link56.company.model.dto.QueryCompanyUserDTO;
|
||||
import cn.link56.company.model.dto.UpdateCompanyUserDTO;
|
||||
import cn.link56.company.model.vo.ChatUserVO;
|
||||
import cn.link56.company.model.vo.CompanyUserProfileVO;
|
||||
import cn.link56.company.model.vo.QueryCompanyUserVO;
|
||||
import cn.link56.company.service.CompanyUserService;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@@ -51,4 +51,10 @@ public class CompanyUserController {
|
||||
List<ChatUserVO> data = companyUserService.listCompanyChatUsers(companyId);
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
@GetMapping("/profile/{userId}")
|
||||
public Result<CompanyUserProfileVO> getCompanyUserProfile(@PathVariable("userId") Long userId){
|
||||
CompanyUserProfileVO companyUserProfileVO =companyUserService.getCompanyUserProfile(userId);
|
||||
return Result.success(companyUserProfileVO);
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,20 @@ import cn.link56.company.model.middle.RecommendCompanyRow;
|
||||
import cn.link56.company.model.po.Company;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Select;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface CompanyMapper extends BaseMapper<Company> {
|
||||
List<RecommendCompanyRow> listRecommend();
|
||||
|
||||
@Select(
|
||||
"SELECT c.* " +
|
||||
"FROM b_company c " +
|
||||
"INNER JOIN b_company_user cu ON cu.company_id = c.id " +
|
||||
"WHERE cu.user_id = #{userId} "
|
||||
)
|
||||
Company selectCompanyByUserId(@Param("userId") Long userId);
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package cn.link56.company.model.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class CompanyUserProfileVO {
|
||||
private Company company;
|
||||
private User user;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class Company{
|
||||
private Long id;
|
||||
private String name;
|
||||
private String logo;
|
||||
private String profile;
|
||||
private List<String> areas;
|
||||
private List<String> industries;
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class User{
|
||||
private Long id;
|
||||
private String name;
|
||||
private String avatar;
|
||||
private String position;
|
||||
private String email;
|
||||
private String phone;
|
||||
private Boolean isOnline;
|
||||
}
|
||||
}
|
||||
+41
@@ -13,6 +13,7 @@ import cn.link56.company.model.dto.UpdateCompanyUserDTO;
|
||||
import cn.link56.company.model.po.Company;
|
||||
import cn.link56.company.model.po.CompanyUser;
|
||||
import cn.link56.company.model.vo.ChatUserVO;
|
||||
import cn.link56.company.model.vo.CompanyUserProfileVO;
|
||||
import cn.link56.company.model.vo.QueryCompanyUserVO;
|
||||
import cn.link56.im.storage.SessionManager;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
@@ -27,6 +28,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -327,4 +329,43 @@ public class CompanyUserService extends ServiceImpl<CompanyUserMapper, CompanyUs
|
||||
return result;
|
||||
}
|
||||
|
||||
public CompanyUserProfileVO getCompanyUserProfile(Long userId) {
|
||||
|
||||
CompanyUser companyUserPO = companyUserMapper.selectById(userId);
|
||||
if (companyUserPO == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CompanyUserProfileVO cardVO = new CompanyUserProfileVO();
|
||||
|
||||
// ===== 用户(联系人)=====
|
||||
CompanyUserProfileVO.User userVO = new CompanyUserProfileVO.User();
|
||||
BeanUtils.copyProperties(companyUserPO, userVO);
|
||||
userVO.setIsOnline(SessionManager.isOnline(userId));
|
||||
|
||||
// ===== 公司(主体)=====
|
||||
Company companyPO = companyMapper.selectCompanyByUserId(userId);
|
||||
CompanyUserProfileVO.Company companyVO = new CompanyUserProfileVO.Company();
|
||||
BeanUtils.copyProperties(companyPO, companyVO);
|
||||
|
||||
if (companyPO.getIndustry() != null) {
|
||||
companyVO.setIndustries(
|
||||
Arrays.stream(companyPO.getIndustry().split(","))
|
||||
.map(String::trim)
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
|
||||
if (companyPO.getArea() != null) {
|
||||
companyVO.setAreas(
|
||||
Arrays.stream(companyPO.getArea().split(","))
|
||||
.map(String::trim)
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
|
||||
cardVO.setUser(userVO);
|
||||
cardVO.setCompany(companyVO);
|
||||
return cardVO;
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import cn.link56.post.model.vo.QueryPublicPostVO;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -47,4 +48,7 @@ public interface PostMapper extends BaseMapper<Post> {
|
||||
@Param("title") String title,
|
||||
@Param("tag") String tag
|
||||
);
|
||||
|
||||
@Update("UPDATE b_post SET b_active_time = GETDATE() WHERE b_id = #{postId}")
|
||||
int updateActiveTime(@Param("postId") Long postId);
|
||||
}
|
||||
@@ -59,4 +59,9 @@ public class Post {
|
||||
fill = FieldFill.INSERT_UPDATE
|
||||
)
|
||||
private Date updateTime;
|
||||
|
||||
@TableField(
|
||||
value = "b_active_time"
|
||||
)
|
||||
private Date activeTime;
|
||||
}
|
||||
+6
@@ -2,6 +2,7 @@ package cn.link56.post.service;
|
||||
|
||||
import cn.link56.common.utils.snowflake.idgen.IdGenerator;
|
||||
import cn.link56.post.mapper.PostCommentMapper;
|
||||
import cn.link56.post.mapper.PostMapper;
|
||||
import cn.link56.post.model.dto.CreatePostCommentDTO;
|
||||
import cn.link56.post.model.po.PostComment;
|
||||
import cn.link56.post.model.po.PostView;
|
||||
@@ -30,6 +31,9 @@ public class PostCommentService extends ServiceImpl<PostCommentMapper, PostComme
|
||||
@Resource
|
||||
private PostStatService postStatService;
|
||||
|
||||
@Resource
|
||||
private PostMapper postMapper;
|
||||
|
||||
/**
|
||||
* 根据文章 id 获取浏览总数
|
||||
*/
|
||||
@@ -134,6 +138,8 @@ public class PostCommentService extends ServiceImpl<PostCommentMapper, PostComme
|
||||
CreatePostCommentVO commentVO = new CreatePostCommentVO();
|
||||
BeanUtils.copyProperties(postComment,commentVO);
|
||||
|
||||
// 更新活跃数量
|
||||
postMapper.updateActiveTime(postComment.getPostId());
|
||||
return commentVO;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import cn.link56.post.model.po.Post;
|
||||
import cn.link56.post.model.po.PostComment;
|
||||
import cn.link56.post.model.vo.*;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
@@ -47,6 +48,7 @@ public class PostService extends ServiceImpl<PostMapper, Post> {
|
||||
post.setId(IdGenerator.nextId());
|
||||
post.setCompanyId(UserContext.getCompanyId());
|
||||
post.setTag(joinTags(dto.getTags()));
|
||||
post.setActiveTime(new Date());
|
||||
|
||||
handleUpdatePublishTime(post, dto.getStatus());
|
||||
|
||||
@@ -71,6 +73,10 @@ public class PostService extends ServiceImpl<PostMapper, Post> {
|
||||
handleUpdatePublishTime(post, dto.getStatus());
|
||||
|
||||
postMapper.updateById(post);
|
||||
|
||||
// 更新活跃时间
|
||||
postMapper.updateActiveTime(post.getId());
|
||||
|
||||
return buildEditPostVO(post);
|
||||
}
|
||||
|
||||
@@ -250,7 +256,6 @@ public class PostService extends ServiceImpl<PostMapper, Post> {
|
||||
List<QueryPublicPostVO> queryEditorPostVOS = postMapper.selectPublicPostPage(offset, limit, title, tag);
|
||||
Long total = postMapper.selectPublicPostTotal(title, tag);
|
||||
|
||||
|
||||
return new BasePageVO<QueryPublicPostVO>(total, queryEditorPostVOS);
|
||||
}
|
||||
|
||||
@@ -275,14 +280,6 @@ public class PostService extends ServiceImpl<PostMapper, Post> {
|
||||
postVO.setViewCount(viewTotal);
|
||||
postVO.setCommentCount(commentTotal);
|
||||
|
||||
PostComment latestCommentByPostId = postCommentService.getLatestCommentByPostId(id);
|
||||
|
||||
if (latestCommentByPostId == null){
|
||||
postVO.setActiveTime(post.getUpdateTime());
|
||||
} else {
|
||||
postVO.setActiveTime(latestCommentByPostId.getCreateTime());
|
||||
}
|
||||
|
||||
return postVO;
|
||||
}
|
||||
|
||||
@@ -300,4 +297,5 @@ public class PostService extends ServiceImpl<PostMapper, Post> {
|
||||
|
||||
return postCompanyVO;
|
||||
}
|
||||
|
||||
}
|
||||
+13
-25
@@ -163,41 +163,28 @@
|
||||
t.commentCount,
|
||||
t.activeTime
|
||||
FROM (
|
||||
SELECT
|
||||
SELECT
|
||||
p.b_id AS id,
|
||||
p.b_title AS title,
|
||||
p.b_tag AS tagsString,
|
||||
p.b_company_id AS companyId,
|
||||
bc.b_name AS companyName,
|
||||
|
||||
ISNULL(pv.viewCount, 0) AS viewCount,
|
||||
ISNULL(pc.commentCount, 0) AS commentCount,
|
||||
ISNULL(ps.b_view_count, 0) AS viewCount,
|
||||
ISNULL(ps.b_comment_count, 0) AS commentCount,
|
||||
|
||||
-- 活跃时间:最后评论时间优先
|
||||
ISNULL(pc.lastCommentTime, p.b_update_time) AS activeTime,
|
||||
p.b_active_time AS activeTime,
|
||||
|
||||
ROW_NUMBER() OVER (
|
||||
ORDER BY ISNULL(pc.lastCommentTime, p.b_update_time) DESC
|
||||
) AS rn
|
||||
ORDER BY p.b_update_time DESC
|
||||
) AS rn
|
||||
FROM b_post p
|
||||
LEFT JOIN b_company bc ON bc.b_id = p.b_company_id
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
b_post_id,
|
||||
COUNT(1) AS viewCount
|
||||
FROM b_post_view
|
||||
GROUP BY b_post_id
|
||||
) pv ON pv.b_post_id = p.b_id
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
b_post_id,
|
||||
COUNT(1) AS commentCount,
|
||||
MAX(b_create_time) AS lastCommentTime
|
||||
FROM b_post_comment
|
||||
GROUP BY b_post_id
|
||||
) pc ON pc.b_post_id = p.b_id
|
||||
LEFT JOIN b_company bc
|
||||
ON bc.b_id = p.b_company_id
|
||||
LEFT JOIN b_post_stat ps
|
||||
ON ps.b_post_id = p.b_id
|
||||
<where>
|
||||
AND p.b_status = 1
|
||||
p.b_status = 1
|
||||
<if test="title != null and title != ''">
|
||||
AND p.b_title LIKE '%' + #{title} + '%'
|
||||
</if>
|
||||
@@ -205,10 +192,11 @@
|
||||
AND p.b_tag LIKE '%' + #{tag} + '%'
|
||||
</if>
|
||||
</where>
|
||||
) t
|
||||
) t
|
||||
WHERE t.rn BETWEEN #{offset} AND #{limit}
|
||||
</select>
|
||||
|
||||
|
||||
<select id="selectPublicPostTotal" resultType="java.lang.Long">
|
||||
SELECT COUNT(1)
|
||||
FROM b_post p
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
VITE_SERVER_BASEURL = 'http://192.168.1.137:8084'
|
||||
VITE_WEBSOCKET_BASEURL = 'ws://192.168.1.137:8090/ws'
|
||||
VITE_SERVER_BASEURL = 'http://127.0.0.1:8084'
|
||||
VITE_WEBSOCKET_BASEURL = 'ws://127.0.0.1:8090/ws'
|
||||
+5
-6
@@ -1,10 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import type { CompanyChatUser } from '@/types/company/user'
|
||||
import { Search } from '@element-plus/icons-vue'
|
||||
import type { ChatCompanyUser } from '@/types/company/user'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { listCompanyChatUsersApi } from '@/request/api/company/user'
|
||||
import { listChatCompanyUsersApi } from '@/request/api/company/user'
|
||||
import { createFriendApi } from '@/request/api/im'
|
||||
import { useUserStore } from '@/store/user'
|
||||
|
||||
@@ -19,12 +18,12 @@ const router = useRouter()
|
||||
const userStore = useUserStore()
|
||||
|
||||
const loading = ref(true)
|
||||
const users = ref<CompanyChatUser[]>([])
|
||||
const users = ref<ChatCompanyUser[]>([])
|
||||
|
||||
function onPopoverShow() {
|
||||
loading.value = true
|
||||
|
||||
listCompanyChatUsersApi(props.companyId).then((res) => {
|
||||
listChatCompanyUsersApi(props.companyId).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
users.value = res.data
|
||||
}
|
||||
@@ -44,7 +43,7 @@ function onPopoverHide() {
|
||||
users.value = []
|
||||
}
|
||||
|
||||
function selectUser(user: CompanyChatUser) {
|
||||
function selectUser(user: ChatCompanyUser) {
|
||||
console.log('选择用户:', user)
|
||||
if (userStore.user.id === user.id) {
|
||||
ElMessage.warning('无法与自己发起聊天')
|
||||
@@ -0,0 +1,36 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
userId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
title="Tips"
|
||||
width="500"
|
||||
>
|
||||
<span>This is a message</span>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.loading-wrapper,
|
||||
.empty-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.empty-wrapper {
|
||||
flex-direction: column;
|
||||
.empty-tip {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -67,9 +67,7 @@ function isActive(path: string) {
|
||||
|
||||
<el-dropdown>
|
||||
<div class="user">
|
||||
<el-badge v-if="websocket.connected.value" :value="12" is-dot>
|
||||
<SafeImage :url="userStore.user.avatar || userStore.company.logo" :size="36" round />
|
||||
</el-badge>
|
||||
<SafeImage :url="userStore.user.avatar || userStore.company.logo" :size="36" round />
|
||||
<div class="info">
|
||||
<span class="name">{{ userStore.user.name }}</span>
|
||||
<span class="company">{{ userStore.company.name }}</span>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AddCompanyUserParams, CompanyChatUser, CompanyUser, DeleteCompanyUserParams, QueryCompanyUserParams, UpdateCompanyUserParams } from '@/types/company/user'
|
||||
import type { AddCompanyUserParams, ChatCompanyUser, CompanyUser, DeleteCompanyUserParams, QueryCompanyUserParams, UpdateCompanyUserParams } from '@/types/company/user'
|
||||
import { httpGet, httpPostJson } from '@/request/utils'
|
||||
|
||||
export function queryCompanyUserApi(params: QueryCompanyUserParams) {
|
||||
@@ -14,6 +14,6 @@ export function deleteCompanyUserApi(params: DeleteCompanyUserParams) {
|
||||
return httpPostJson<void>('/company/user/delete', params)
|
||||
}
|
||||
|
||||
export function listCompanyChatUsersApi(companyId: number) {
|
||||
return httpGet<CompanyChatUser[]>(`/company/user/chat/${companyId}`)
|
||||
export function listChatCompanyUsersApi(companyId: number) {
|
||||
return httpGet<ChatCompanyUser[]>(`/company/user/chat/${companyId}`)
|
||||
}
|
||||
@@ -13,8 +13,11 @@ declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AnchorMenu: typeof import('./../components/anchor-menu/index.vue')['default']
|
||||
AreaSelect: typeof import('./../components/AreaSelect.vue')['default']
|
||||
ChatCompanyUserSelect: typeof import('../components/ChatUserSelect.vue')['default']
|
||||
ChatCompanyUserCard: typeof import('./../components/ChatCompanyUserCard.vue')['default']
|
||||
ChatCompanyUserSelect: typeof i../components/ChatCompanyUserCard.vuet.vue')['default']
|
||||
ChatUserSelect: typeof import('./../components/ChatUserSelect.vue')['default']
|
||||
CompanyUserCard: typeof import('./../components/CompanyUserCard.vue')['default']
|
||||
CompanyUserPopover: typeof import('./../components/CompanyUserPopover.vue')['default']
|
||||
CustomEmpty: typeof import('./../components/CustomEmpty.vue')['default']
|
||||
CustomLoading: typeof import('./../components/CustomLoading.vue')['default']
|
||||
Directory: typeof import('./../components/wang-editor/modules/directory.vue')['default']
|
||||
|
||||
@@ -58,10 +58,30 @@ export interface DeleteCompanyUserParams {
|
||||
ids: number[]
|
||||
}
|
||||
|
||||
export interface CompanyChatUser {
|
||||
export interface ChatCompanyUser {
|
||||
id: number
|
||||
name: string
|
||||
position: string
|
||||
avatar: string
|
||||
isOnline: boolean
|
||||
}
|
||||
|
||||
export interface CompanyUserProfile {
|
||||
company: {
|
||||
id: number
|
||||
name: string
|
||||
logo: string
|
||||
profile: string
|
||||
areas: string[]
|
||||
industries: string[]
|
||||
}
|
||||
user: {
|
||||
id: number
|
||||
name: string
|
||||
position: string
|
||||
avatar: string
|
||||
email: string
|
||||
phone: string
|
||||
isOnline: boolean
|
||||
}
|
||||
}
|
||||
@@ -54,11 +54,10 @@ export interface Post {
|
||||
tags: string[]
|
||||
|
||||
status: number
|
||||
updateTime: string
|
||||
publishTime: string
|
||||
|
||||
viewCount: number
|
||||
likeCount: number
|
||||
favoriteCount: number
|
||||
commentCount: number
|
||||
|
||||
activeTime: string
|
||||
}
|
||||
@@ -193,7 +193,7 @@ function handleToIm(user: CompanyDetailUser) {
|
||||
<div class="anchor-title">
|
||||
导航
|
||||
</div>
|
||||
<el-anchor :offset="70">
|
||||
<el-anchor :offset="60">
|
||||
<AnchorMenu :menu="anchorMenu" />
|
||||
</el-anchor>
|
||||
</el-affix>
|
||||
|
||||
@@ -14,7 +14,10 @@ import { generateUUID } from '@/utils/uuid'
|
||||
const props = defineProps({
|
||||
group: { type: Object as PropType<ImGroup>, required: true },
|
||||
})
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'avatarClick', id: number): void
|
||||
(e: 'userClick', groupUser: ImGroupUser): void
|
||||
}>()
|
||||
// websocket
|
||||
const websocket = useWebSocket()
|
||||
|
||||
@@ -270,6 +273,14 @@ async function sendMessage() {
|
||||
await nextTick()
|
||||
scrollToLatestMessage()
|
||||
}
|
||||
|
||||
function handleClickAvatar(userId: number) {
|
||||
emits('avatarClick', userId)
|
||||
}
|
||||
|
||||
function handleClickGroupUser(groupUser: ImGroupUser) {
|
||||
emits('userClick', groupUser)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -293,7 +304,7 @@ async function sendMessage() {
|
||||
? 'im__chat-message--from'
|
||||
: 'im__chat-message--to'"
|
||||
>
|
||||
<div class="im__chat-message-avatar">
|
||||
<div class="im__chat-message-avatar" @click="handleClickAvatar(item.senderId)">
|
||||
<SafeImage :url="item.senderAvatar" :size="40" round />
|
||||
</div>
|
||||
<div class="im__chat-message-body">
|
||||
@@ -363,7 +374,7 @@ async function sendMessage() {
|
||||
</div>
|
||||
|
||||
<div class="im__group-section-body">
|
||||
<div v-for="item in groupUsers" :key="item.userId" class="im__group-item">
|
||||
<div v-for="item in groupUsers" :key="item.userId" class="im__group-item" @click="handleClickGroupUser(item)">
|
||||
<el-badge
|
||||
is-dot
|
||||
:color="item.isOnline ? '#24e68a' : '#bfbfbf'"
|
||||
|
||||
@@ -15,6 +15,10 @@ const props = defineProps({
|
||||
friend: { type: Object as PropType<ImFriend>, required: true },
|
||||
})
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'avatarClick', id: number): void
|
||||
}>()
|
||||
|
||||
// websocket
|
||||
const websocket = useWebSocket()
|
||||
|
||||
@@ -253,6 +257,10 @@ async function sendMessage() {
|
||||
await nextTick()
|
||||
scrollToLatestMessage()
|
||||
}
|
||||
|
||||
function handleClickAvatar(userId: number) {
|
||||
emits('avatarClick', userId)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -276,8 +284,8 @@ async function sendMessage() {
|
||||
? 'im__chat-message--from'
|
||||
: 'im__chat-message--to'"
|
||||
>
|
||||
<div class="im__chat-message-avatar">
|
||||
<SafeImage :url=" userStore.user.id === item.senderId ? userStore.user.avatar : props.friend.friendAvatar" :size="40" round />
|
||||
<div class="im__chat-message-avatar" @click="handleClickAvatar(item.senderId)">
|
||||
<SafeImage :url="userStore.user.id === item.senderId ? userStore.user.avatar : props.friend.friendAvatar" :size="40" round />
|
||||
</div>
|
||||
<div class="im__chat-message-body">
|
||||
<div class="im__chat-message-name">
|
||||
|
||||
@@ -162,7 +162,7 @@ function getTotalCommentCount(list: PostComment[]): number {
|
||||
<template v-else>
|
||||
<div class="post-left">
|
||||
<el-affix :offset="90">
|
||||
<el-anchor>
|
||||
<el-anchor :offset="60">
|
||||
<AnchorMenu :menu="anchorMenu" />
|
||||
</el-anchor>
|
||||
</el-affix>
|
||||
|
||||
@@ -32,11 +32,11 @@ const menuList: NavMenuItem [] = [
|
||||
index: '/settings/creator',
|
||||
icon: 'hugeicons:edit-01',
|
||||
children: [
|
||||
{
|
||||
label: '首页',
|
||||
index: '/settings/creator/home',
|
||||
icon: 'hugeicons:home-03',
|
||||
},
|
||||
// {
|
||||
// label: '首页',
|
||||
// index: '/settings/creator/home',
|
||||
// icon: 'hugeicons:home-03',
|
||||
// },
|
||||
{
|
||||
label: '写文章',
|
||||
index: '/settings/creator/editor',
|
||||
|
||||
@@ -1,246 +1,154 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>私聊界面示例</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #f5f5f5;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
}
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC";
|
||||
background: #f0f2f5;
|
||||
}
|
||||
|
||||
/* 左侧聊天列表 */
|
||||
.chat-list {
|
||||
width: 280px;
|
||||
background-color: #fff;
|
||||
border-right: 1px solid #ddd;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.mask {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0,0,0,.45);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.chat-list .user-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
.modal {
|
||||
width: 480px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 12px 32px rgba(0,0,0,.18);
|
||||
}
|
||||
|
||||
.chat-list .user-card:hover,
|
||||
.chat-list .user-card.active {
|
||||
background-color: #f0f8ff;
|
||||
}
|
||||
/* 用户区 */
|
||||
.header {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.chat-list .avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
margin-right: 12px;
|
||||
object-fit: cover;
|
||||
border: 2px solid #4caf50;
|
||||
/* 默认在线状态绿色 */
|
||||
}
|
||||
.avatar {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
border-radius: 50%;
|
||||
background: #ddd;
|
||||
}
|
||||
|
||||
.chat-list .user-info {
|
||||
flex: 1;
|
||||
}
|
||||
.user-main {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.chat-list .user-name {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.user-line {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.chat-list .user-position {
|
||||
margin: 2px 0 0 0;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
.user-line .role {
|
||||
font-weight: 400;
|
||||
color: #595959;
|
||||
}
|
||||
|
||||
.chat-list .status {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
background-color: #4caf50;
|
||||
}
|
||||
.status {
|
||||
font-size: 12px;
|
||||
color: #52c41a;
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
.chat-list .offline {
|
||||
background-color: #ccc;
|
||||
}
|
||||
.contact-line {
|
||||
margin-top: 6px;
|
||||
font-size: 13px;
|
||||
color: #595959;
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* 右侧聊天详情 */
|
||||
.chat-detail {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
.action {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
background-color: #fff;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
.btn {
|
||||
padding: 6px 14px;
|
||||
border-radius: 6px;
|
||||
border: none;
|
||||
background: #1677ff;
|
||||
color: #fff;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chat-header .avatar {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50%;
|
||||
margin-right: 12px;
|
||||
border: 2px solid #4caf50;
|
||||
}
|
||||
/* 公司区 */
|
||||
.company {
|
||||
padding: 14px 20px 18px;
|
||||
}
|
||||
|
||||
.chat-header .info {
|
||||
flex: 1;
|
||||
}
|
||||
.company-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #262626;
|
||||
}
|
||||
|
||||
.chat-header .info .name {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
margin: 0;
|
||||
}
|
||||
.company-meta {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
|
||||
.chat-header .info .position {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin: 2px 0;
|
||||
}
|
||||
.desc {
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
color: #595959;
|
||||
line-height: 1.6;
|
||||
|
||||
.chat-header .info .company-info {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
.chat-header .info .company-logo {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-right: 4px;
|
||||
border-radius: 2px;
|
||||
object-fit: cover;
|
||||
}
|
||||
<body>
|
||||
<div class="mask">
|
||||
<div class="modal">
|
||||
|
||||
.messages {
|
||||
flex: 1;
|
||||
padding: 16px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-bottom: 12px;
|
||||
max-width: 70%;
|
||||
padding: 8px 12px;
|
||||
border-radius: 12px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.message.self {
|
||||
background-color: #dcf8c6;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.message.other {
|
||||
background-color: #fff;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.chat-input {
|
||||
padding: 12px 16px;
|
||||
border-top: 1px solid #ddd;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.chat-input input {
|
||||
flex: 1;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 20px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.chat-input button {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
background-color: #4caf50;
|
||||
color: #fff;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!-- 左侧聊天列表 -->
|
||||
<div class="chat-list">
|
||||
<div class="user-card active">
|
||||
<img class="avatar" src="https://i.pravatar.cc/40?img=1" alt="头像" />
|
||||
<div class="user-info">
|
||||
<p class="user-name">张三</p>
|
||||
<p class="user-position">前端工程师</p>
|
||||
<!-- 用户 -->
|
||||
<div class="header">
|
||||
<div class="avatar"></div>
|
||||
<div class="user-main">
|
||||
<div class="user-line">
|
||||
张三 · <span class="role">产品经理</span>
|
||||
<span class="status">● 在线</span>
|
||||
</div>
|
||||
<div class="status"></div>
|
||||
</div>
|
||||
|
||||
<div class="user-card">
|
||||
<img class="avatar" src="https://i.pravatar.cc/40?img=2" alt="头像" />
|
||||
<div class="user-info">
|
||||
<p class="user-name">李四</p>
|
||||
<p class="user-position">产品经理</p>
|
||||
<div class="contact-line">
|
||||
<span>📧 zhangsan@company.com</span>
|
||||
<span>📞 138****8888</span>
|
||||
</div>
|
||||
<div class="status offline"></div>
|
||||
</div>
|
||||
|
||||
<div class="user-card">
|
||||
<img class="avatar" src="https://i.pravatar.cc/40?img=3" alt="头像" />
|
||||
<div class="user-info">
|
||||
<p class="user-name">王五</p>
|
||||
<p class="user-position">设计师</p>
|
||||
<div class="action">
|
||||
<button class="btn">发起聊天</button>
|
||||
</div>
|
||||
<div class="status"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧聊天详情 -->
|
||||
<div class="chat-detail">
|
||||
<!-- 聊天头部 -->
|
||||
<div class="chat-header">
|
||||
<img class="avatar" src="https://i.pravatar.cc/50?img=1" alt="头像" />
|
||||
<div class="info">
|
||||
<p class="name">张三</p>
|
||||
<p class="position">前端工程师</p>
|
||||
<div class="company-info">
|
||||
<img class="company-logo" src="https://via.placeholder.com/16" alt="公司logo" />
|
||||
<span>ABC科技公司</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 消息区域 -->
|
||||
<div class="messages">
|
||||
<div class="message other">你好!这是张三的消息。</div>
|
||||
<div class="message self">你好,我是自己发的消息。</div>
|
||||
<div class="message other">我们下午开会吗?</div>
|
||||
<div class="message self">好的,下午三点。</div>
|
||||
</div>
|
||||
|
||||
<!-- 输入框 -->
|
||||
<div class="chat-input">
|
||||
<input type="text" placeholder="输入消息..." />
|
||||
<button>发送</button>
|
||||
<!-- 公司 -->
|
||||
<div class="company">
|
||||
<div class="company-name">北京某某科技有限公司</div>
|
||||
<div class="company-meta">互联网 / SaaS · 北京</div>
|
||||
<div class="desc">
|
||||
专注于为中大型企业提供数字化解决方案,覆盖供应链、协同办公与数据分析等多个领域,
|
||||
服务客户超过 3000 家。
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -41,7 +41,7 @@ export default defineConfig((configEnv) => {
|
||||
],
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
port: 8089,
|
||||
port: 8080,
|
||||
open: true,
|
||||
// 用于配置开发服务器启动时预加载的一些客户端文件,以提高初次加载的速度。
|
||||
warmup: {
|
||||
|
||||
Reference in new issue
Block a user