This commit is contained in:
oneao committed 2026-01-03 22:02:11 +08:00
1 parent c26d1b3c81
commit fac3206497
24 files changed
+364 -284

No files matched your search

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