u
This commit is contained in:
1 parent
fac3206497
commit
dc885119a0
43 files changed
+927
-364
No files matched your search
-22
@@ -1,22 +0,0 @@
|
||||
package cn.link56.base.controller;
|
||||
|
||||
import cn.link56.base.model.vo.BaseDataVO;
|
||||
import cn.link56.base.service.BaseDataService;
|
||||
import cn.link56.common.Result;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/base")
|
||||
public class BaseDataController {
|
||||
@Resource
|
||||
private BaseDataService baseDataService;
|
||||
|
||||
@GetMapping("/init")
|
||||
public Result<BaseDataVO> initBaseData() {
|
||||
return Result.success(baseDataService.getBaseData());
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package cn.link56.base.model.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class BaseDataVO {
|
||||
private List<Industry> industries;
|
||||
private List<Area> areas;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class Industry {
|
||||
private Long id;
|
||||
private Integer xh;
|
||||
private String name;
|
||||
}
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public static class Area {
|
||||
private String id;
|
||||
private Integer xh;
|
||||
private String name;
|
||||
private String fatherId;
|
||||
private List<Area> children;
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
package cn.link56.base.service;
|
||||
|
||||
import cn.link56.base.mapper.AreaMapper;
|
||||
import cn.link56.base.mapper.IndustryMapper;
|
||||
import cn.link56.base.model.po.Area;
|
||||
import cn.link56.base.model.po.Industry;
|
||||
import cn.link56.base.model.vo.BaseDataVO;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class BaseDataService {
|
||||
@Resource
|
||||
private IndustryMapper IndustryMapper;
|
||||
@Resource
|
||||
private AreaMapper AreaMapper;
|
||||
|
||||
public BaseDataVO getBaseData() {
|
||||
BaseDataVO baseDataVO = new BaseDataVO();
|
||||
|
||||
// 查询可用行业
|
||||
List<BaseDataVO.Industry> industries = IndustryMapper.selectList(
|
||||
new LambdaQueryWrapper<Industry>()
|
||||
.eq(Industry::getCanuse, "1")
|
||||
).stream()
|
||||
.map(industry -> new BaseDataVO.Industry(
|
||||
industry.getId(),
|
||||
industry.getXh(),
|
||||
industry.getName()
|
||||
))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
baseDataVO.setIndustries(industries);
|
||||
|
||||
// 地区
|
||||
List<Area> allAreas = AreaMapper.listAllArea();
|
||||
List<BaseDataVO.Area> areaTree = buildAreaTree(allAreas);
|
||||
|
||||
baseDataVO.setAreas(areaTree);
|
||||
return baseDataVO;
|
||||
}
|
||||
|
||||
/**
|
||||
* BArea → BaseDataVO.Area 树
|
||||
*/
|
||||
private List<BaseDataVO.Area> buildAreaTree(List<Area> areas) {
|
||||
if (areas == null || areas.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// id -> VO
|
||||
Map<String, BaseDataVO.Area> map = new HashMap<>(areas.size());
|
||||
|
||||
// 1️⃣ 先全部转 VO
|
||||
for (Area a : areas) {
|
||||
BaseDataVO.Area vo = new BaseDataVO.Area(
|
||||
a.getId(),
|
||||
a.getXh(),
|
||||
a.getName(),
|
||||
a.getFatherId(),
|
||||
new ArrayList<>()
|
||||
);
|
||||
map.put(vo.getId(), vo);
|
||||
}
|
||||
|
||||
// 2️⃣ 组装父子关系
|
||||
List<BaseDataVO.Area> roots = new ArrayList<>();
|
||||
|
||||
for (BaseDataVO.Area area : map.values()) {
|
||||
String parentId = area.getFatherId();
|
||||
if (parentId == null || parentId.isEmpty()) {
|
||||
roots.add(area);
|
||||
} else {
|
||||
BaseDataVO.Area parent = map.get(parentId);
|
||||
if (parent != null) {
|
||||
parent.getChildren().add(area);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3️⃣ 可选排序
|
||||
sortTree(roots);
|
||||
|
||||
return roots;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 xh 排序(如果你用 order,也可以换)
|
||||
*/
|
||||
private void sortTree(List<BaseDataVO.Area> nodes) {
|
||||
if (nodes == null || nodes.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nodes.sort(Comparator.comparing(
|
||||
BaseDataVO.Area::getXh,
|
||||
Comparator.nullsLast(Integer::compareTo)
|
||||
));
|
||||
|
||||
for (BaseDataVO.Area node : nodes) {
|
||||
sortTree(node.getChildren());
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
-4
@@ -4,10 +4,7 @@ import cn.link56.common.BasePageVO;
|
||||
import cn.link56.common.Result;
|
||||
import cn.link56.company.model.dto.QueryCompanyDTO;
|
||||
import cn.link56.company.model.dto.UpdateCompanyDTO;
|
||||
import cn.link56.company.model.vo.CompanyDetailVO;
|
||||
import cn.link56.company.model.vo.QueryCompanyVO;
|
||||
import cn.link56.company.model.vo.RecommendCompanyVO;
|
||||
import cn.link56.company.model.vo.RecommendHotCompanyVO;
|
||||
import cn.link56.company.model.vo.*;
|
||||
import cn.link56.company.service.CompanyService;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
@@ -61,4 +58,13 @@ public class CompanyController {
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@GetMapping("/area")
|
||||
public Result<List<AreaVO>> listAreas(){
|
||||
return Result.success(companyService.listAreas());
|
||||
}
|
||||
|
||||
@GetMapping("/industry")
|
||||
public Result<List<IndustryVO>> listIndustries(){
|
||||
return Result.success(companyService.listIndustries());
|
||||
}
|
||||
}
|
||||
+7
-5
@@ -2,6 +2,7 @@ package cn.link56.company.controller;
|
||||
|
||||
import cn.link56.common.BasePageVO;
|
||||
import cn.link56.common.Result;
|
||||
import cn.link56.common.context.UserContext;
|
||||
import cn.link56.company.model.dto.AddCompanyUserDTO;
|
||||
import cn.link56.company.model.dto.DeleteCompanyUserDTO;
|
||||
import cn.link56.company.model.dto.QueryCompanyUserDTO;
|
||||
@@ -29,32 +30,33 @@ public class CompanyUserController {
|
||||
}
|
||||
|
||||
@PostMapping("/add")
|
||||
public Result<Void> addCompanyUser(@RequestBody @Validated AddCompanyUserDTO companyUserDTO){
|
||||
public Result<Void> addCompanyUser(@RequestBody @Validated AddCompanyUserDTO companyUserDTO) {
|
||||
companyUserService.addCompanyUser(companyUserDTO);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@PostMapping("/update")
|
||||
public Result<Void> updateCompanyUser(@RequestBody @Validated UpdateCompanyUserDTO companyUserDTO){
|
||||
public Result<Void> updateCompanyUser(@RequestBody @Validated UpdateCompanyUserDTO companyUserDTO) {
|
||||
companyUserService.updateCompanyUser(companyUserDTO);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@PostMapping("/delete")
|
||||
public Result<Void> deleteCompanyUser(@RequestBody @Validated DeleteCompanyUserDTO deleteCompanyUserDTO){
|
||||
public Result<Void> deleteCompanyUser(@RequestBody @Validated DeleteCompanyUserDTO deleteCompanyUserDTO) {
|
||||
companyUserService.deleteCompanyUser(deleteCompanyUserDTO);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@GetMapping("/chat/{companyId}")
|
||||
public Result<List<ChatUserVO>> listCompanyChatUsers(@PathVariable("companyId") Long companyId){
|
||||
public Result<List<ChatUserVO>> listCompanyChatUsers(@PathVariable("companyId") Long companyId) {
|
||||
List<ChatUserVO> data = companyUserService.listCompanyChatUsers(companyId);
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
@GetMapping("/profile/{userId}")
|
||||
public Result<CompanyUserProfileVO> getCompanyUserProfile(@PathVariable("userId") Long userId){
|
||||
public Result<CompanyUserProfileVO> getCompanyUserProfile(@PathVariable("userId") Long userId) {
|
||||
CompanyUserProfileVO companyUserProfileVO =companyUserService.getCompanyUserProfile(userId);
|
||||
return Result.success(companyUserProfileVO);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -13,11 +13,5 @@ import java.util.List;
|
||||
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);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package cn.link56.company.model.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class AreaVO {
|
||||
private String id;
|
||||
private Integer xh;
|
||||
private String name;
|
||||
private String fatherId;
|
||||
private List<AreaVO> children;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package cn.link56.company.model.vo;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class IndustryVO {
|
||||
private Long id;
|
||||
private Integer xh;
|
||||
private String name;
|
||||
}
|
||||
+92
-6
@@ -1,5 +1,9 @@
|
||||
package cn.link56.company.service;
|
||||
|
||||
import cn.link56.base.mapper.AreaMapper;
|
||||
import cn.link56.base.mapper.IndustryMapper;
|
||||
import cn.link56.base.model.po.Area;
|
||||
import cn.link56.base.model.po.Industry;
|
||||
import cn.link56.common.BasePageVO;
|
||||
import cn.link56.common.context.UserContext;
|
||||
import cn.link56.common.exception.BusinessException;
|
||||
@@ -9,10 +13,7 @@ import cn.link56.company.model.dto.UpdateCompanyDTO;
|
||||
import cn.link56.company.model.middle.RecommendCompanyRow;
|
||||
import cn.link56.company.model.po.Company;
|
||||
import cn.link56.company.model.po.CompanyUser;
|
||||
import cn.link56.company.model.vo.CompanyDetailVO;
|
||||
import cn.link56.company.model.vo.QueryCompanyVO;
|
||||
import cn.link56.company.model.vo.RecommendCompanyVO;
|
||||
import cn.link56.company.model.vo.RecommendHotCompanyVO;
|
||||
import cn.link56.company.model.vo.*;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
@@ -23,8 +24,7 @@ import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
@@ -33,6 +33,10 @@ public class CompanyService extends ServiceImpl<CompanyMapper, Company> {
|
||||
private CompanyMapper companyMapper;
|
||||
@Resource
|
||||
private CompanyUserService companyUserService;
|
||||
@Resource
|
||||
private IndustryMapper industryMapper;
|
||||
@Resource
|
||||
private AreaMapper areaMapper;
|
||||
|
||||
public List<RecommendCompanyVO> listRecommend() {
|
||||
List<RecommendCompanyRow> rows = companyMapper.listRecommend();
|
||||
@@ -242,4 +246,86 @@ public class CompanyService extends ServiceImpl<CompanyMapper, Company> {
|
||||
|
||||
companyUserService.updateCompanyAvatar(company.getId(),company.getLogo());
|
||||
}
|
||||
|
||||
public List<IndustryVO> listIndustries() {
|
||||
LambdaQueryWrapper<Industry> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(Industry::getCanuse,"1");
|
||||
List<Industry> industries = industryMapper.selectList(queryWrapper);
|
||||
|
||||
return industries.stream()
|
||||
.map(industry -> new IndustryVO(
|
||||
industry.getId(),
|
||||
industry.getXh(),
|
||||
industry.getName()
|
||||
))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public List<AreaVO> listAreas() {
|
||||
List<Area> allAreas = areaMapper.listAllArea();
|
||||
|
||||
return buildAreaTree(allAreas);
|
||||
}
|
||||
|
||||
/**
|
||||
* BArea → Area 树
|
||||
*/
|
||||
private List<AreaVO> buildAreaTree(List<Area> areas) {
|
||||
if (areas == null || areas.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
// id -> VO
|
||||
Map<String, AreaVO> map = new HashMap<>(areas.size());
|
||||
|
||||
// 1️⃣ 先全部转 VO
|
||||
for (Area a : areas) {
|
||||
AreaVO vo = new AreaVO(
|
||||
a.getId(),
|
||||
a.getXh(),
|
||||
a.getName(),
|
||||
a.getFatherId(),
|
||||
new ArrayList<>()
|
||||
);
|
||||
map.put(vo.getId(), vo);
|
||||
}
|
||||
|
||||
// 2️⃣ 组装父子关系
|
||||
List<AreaVO> roots = new ArrayList<>();
|
||||
|
||||
for (AreaVO area : map.values()) {
|
||||
String parentId = area.getFatherId();
|
||||
if (parentId == null || parentId.isEmpty()) {
|
||||
roots.add(area);
|
||||
} else {
|
||||
AreaVO parent = map.get(parentId);
|
||||
if (parent != null) {
|
||||
parent.getChildren().add(area);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3️⃣ 可选排序
|
||||
sortTree(roots);
|
||||
|
||||
return roots;
|
||||
}
|
||||
|
||||
/**
|
||||
* 按 xh 排序(如果你用 order,也可以换)
|
||||
*/
|
||||
private void sortTree(List<AreaVO> nodes) {
|
||||
if (nodes == null || nodes.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
nodes.sort(Comparator.comparing(
|
||||
AreaVO::getXh,
|
||||
Comparator.nullsLast(Integer::compareTo)
|
||||
));
|
||||
|
||||
for (AreaVO node : nodes) {
|
||||
sortTree(node.getChildren());
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-5
@@ -15,6 +15,9 @@ 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.mapper.ImFriendMapper;
|
||||
import cn.link56.im.mapper.ImGroupMapper;
|
||||
import cn.link56.im.mapper.ImGroupUserMapper;
|
||||
import cn.link56.im.storage.SessionManager;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
@@ -330,13 +333,12 @@ public class CompanyUserService extends ServiceImpl<CompanyUserMapper, CompanyUs
|
||||
}
|
||||
|
||||
public CompanyUserProfileVO getCompanyUserProfile(Long userId) {
|
||||
|
||||
CompanyUser companyUserPO = companyUserMapper.selectById(userId);
|
||||
if (companyUserPO == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
CompanyUserProfileVO cardVO = new CompanyUserProfileVO();
|
||||
CompanyUserProfileVO profileVO = new CompanyUserProfileVO();
|
||||
|
||||
// ===== 用户(联系人)=====
|
||||
CompanyUserProfileVO.User userVO = new CompanyUserProfileVO.User();
|
||||
@@ -364,8 +366,9 @@ public class CompanyUserService extends ServiceImpl<CompanyUserMapper, CompanyUs
|
||||
);
|
||||
}
|
||||
|
||||
cardVO.setUser(userVO);
|
||||
cardVO.setCompany(companyVO);
|
||||
return cardVO;
|
||||
profileVO.setUser(userVO);
|
||||
profileVO.setCompany(companyVO);
|
||||
return profileVO;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -71,7 +71,7 @@ public class ImController {
|
||||
|
||||
@PostMapping("/private/update/unread")
|
||||
public Result<Void> updatePrivateUnread(@RequestBody UpdatePrivateUnreadDTO dto){
|
||||
Long userId = dto.getUserId();
|
||||
Long userId = UserContext.getUserId();
|
||||
Long friendId = dto.getFriendId();
|
||||
|
||||
imFriendService.decreaseUnreadCount(userId,friendId);
|
||||
@@ -81,7 +81,7 @@ public class ImController {
|
||||
|
||||
@PostMapping("/group/update/unread")
|
||||
public Result<Void> updateGroup(@RequestBody UpdateGroupUnreadDTO dto){
|
||||
Long userId = dto.getUserId();
|
||||
Long userId = UserContext.getUserId();
|
||||
Long groupId = dto.getGroupId();
|
||||
|
||||
imGroupUserService.decreaseUnreadCount(groupId,userId);
|
||||
@@ -103,4 +103,14 @@ public class ImController {
|
||||
|
||||
return Result.success(groupProfileVO);
|
||||
}
|
||||
|
||||
@GetMapping("/unreadCount")
|
||||
public Result<Long> getAllUnreadCount(){
|
||||
Long userId = UserContext.getUserId();
|
||||
|
||||
Long friendUnread = imFriendService.getAllUnreadCount(userId);
|
||||
Long groupUnread = imGroupUserService.getAllUnreadCount(userId);
|
||||
|
||||
return Result.success(friendUnread + groupUnread);
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import cn.link56.im.model.vo.QueryFriendVO;
|
||||
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;
|
||||
|
||||
@@ -41,4 +42,8 @@ public interface ImFriendMapper extends BaseMapper<ImFriend> {
|
||||
@Param("userId") Long userId,
|
||||
@Param("friendId") Long friendId
|
||||
);
|
||||
|
||||
|
||||
@Select("SELECT SUM(b_unread_count) FROM b_im_friend WHERE b_user_id = #{userId}")
|
||||
Long selectAllUnreadCount(@Param("userId") Long userId);
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import cn.link56.im.model.vo.QueryGroupVO;
|
||||
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;
|
||||
|
||||
@@ -19,10 +20,14 @@ public interface ImGroupMapper extends BaseMapper<ImGroup> {
|
||||
@Param("id") Long id
|
||||
);
|
||||
|
||||
Long selectGroupTotal(@Param("userId") Long userId,
|
||||
@Param("name") String name);
|
||||
Long selectGroupTotal(
|
||||
@Param("userId") Long userId,
|
||||
@Param("name") String name
|
||||
);
|
||||
|
||||
GroupProfileVO selectGroupProfile(
|
||||
@Param("groupId") Long groupId,
|
||||
@Param("userId") Long userId);
|
||||
@Param("userId") Long userId
|
||||
);
|
||||
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import cn.link56.im.model.vo.QueryGroupUserVO;
|
||||
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;
|
||||
|
||||
@@ -14,4 +15,8 @@ public interface ImGroupUserMapper extends BaseMapper<ImGroupUser> {
|
||||
@Param("groupId") Long groupId,
|
||||
@Param("userName") String userName
|
||||
);
|
||||
|
||||
|
||||
@Select("SELECT SUM(b_unread_count) FROM b_im_group_user WHERE b_user_id = #{userId}")
|
||||
Long selectAllUnreadCount(@Param("userId") Long userId);
|
||||
}
|
||||
-1
@@ -9,5 +9,4 @@ import lombok.NoArgsConstructor;
|
||||
@NoArgsConstructor
|
||||
public class UpdateGroupUnreadDTO {
|
||||
private Long groupId;
|
||||
private Long userId;
|
||||
}
|
||||
-1
@@ -8,6 +8,5 @@ import lombok.NoArgsConstructor;
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class UpdatePrivateUnreadDTO {
|
||||
private Long userId;
|
||||
private Long friendId;
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
@@ -11,5 +13,12 @@ public class FriendProfileVO {
|
||||
private Long friendId;
|
||||
private String friendName;
|
||||
private String friendAvatar;
|
||||
|
||||
private Date lastMessageTime;
|
||||
private Integer lastMessageType;
|
||||
private String lastMessageContent;
|
||||
|
||||
private Integer unreadCount;
|
||||
|
||||
private Boolean isOnline;
|
||||
}
|
||||
@@ -25,7 +25,7 @@ public class ImFriendService extends ServiceImpl<ImFriendMapper, ImFriend> {
|
||||
private ImFriendMapper imFriendMapper;
|
||||
|
||||
public void createFriend(Long userId,Long friendId){
|
||||
// 先检查是否存在
|
||||
// 先检查是否存在好友关系
|
||||
LambdaQueryWrapper<ImFriend> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.eq(ImFriend::getUserId,userId)
|
||||
.eq(ImFriend::getFriendId,friendId);
|
||||
@@ -108,6 +108,14 @@ public class ImFriendService extends ServiceImpl<ImFriendMapper, ImFriend> {
|
||||
public FriendProfileVO getFriendProfile(Long friendId) {
|
||||
Long userId = UserContext.getUserId();
|
||||
|
||||
return imFriendMapper.selectFriendProfile(userId, friendId);
|
||||
FriendProfileVO friendProfileVO = imFriendMapper.selectFriendProfile(userId, friendId);
|
||||
friendProfileVO.setIsOnline(SessionManager.isOnline(friendId));
|
||||
|
||||
return friendProfileVO;
|
||||
}
|
||||
|
||||
|
||||
public Long getAllUnreadCount(Long userId){
|
||||
return imFriendMapper.selectAllUnreadCount(userId);
|
||||
}
|
||||
}
|
||||
@@ -50,4 +50,6 @@ public class ImGroupService extends ServiceImpl<ImGroupMapper, ImGroup>{
|
||||
|
||||
return imGroupMapper.selectGroupProfile(groupId,userId);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -94,4 +94,7 @@ public class ImGroupUserService extends ServiceImpl<ImGroupUserMapper, ImGroupUs
|
||||
imGroupUserMapper.update(updateWrapper);
|
||||
}
|
||||
|
||||
public Long getAllUnreadCount(Long userId){
|
||||
return imGroupUserMapper.selectAllUnreadCount(userId);
|
||||
}
|
||||
}
|
||||
+16
@@ -30,5 +30,21 @@
|
||||
sheng.b_order ASC,
|
||||
shi.b_order ASC
|
||||
</select>
|
||||
|
||||
|
||||
<select id="selectCompanyByUserId" resultType="cn.link56.company.model.po.Company">
|
||||
SELECT
|
||||
c.b_id as id,
|
||||
c.b_name as name,
|
||||
c.b_logo as logo,
|
||||
c.b_profile as profile,
|
||||
c.b_area as area,
|
||||
c.b_industry as industry
|
||||
FROM
|
||||
b_company c
|
||||
INNER JOIN b_company_user cu ON cu.b_company_id = c.b_id
|
||||
WHERE
|
||||
cu.b_id = #{userId}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
+11
-6
@@ -13,7 +13,7 @@
|
||||
u.b_name AS friendName,
|
||||
u.b_avatar AS friendAvatar,
|
||||
f.b_unread_count AS unreadCount,
|
||||
m.b_send_time AS lastMessageTime,
|
||||
m.b_send_time AS lastMessageTime,
|
||||
m.b_type AS lastMessageType,
|
||||
m.b_content AS lastMessageContent,
|
||||
ROW_NUMBER() OVER (
|
||||
@@ -64,12 +64,17 @@
|
||||
|
||||
<select id="selectFriendProfile" resultType="cn.link56.im.model.vo.FriendProfileVO">
|
||||
SELECT
|
||||
f.b_friend_id AS friendId,
|
||||
u.b_name AS friendName,
|
||||
u.b_avatar AS friendAvatar,
|
||||
f.b_unread_count AS unreadCount
|
||||
f.b_friend_id AS friendId,
|
||||
u.b_name AS friendName,
|
||||
u.b_avatar AS friendAvatar,
|
||||
f.b_unread_count AS unreadCount,
|
||||
m.b_send_time AS lastMessageTime,
|
||||
m.b_type AS lastMessageType,
|
||||
m.b_content AS lastMessageContent
|
||||
FROM b_im_friend f
|
||||
INNER JOIN b_company_user u ON f.b_friend_id = u.b_id
|
||||
INNER JOIN b_company_user u ON f.b_friend_id = u.b_id
|
||||
LEFT JOIN b_im_private_message m ON f.b_last_message_id = m.b_id
|
||||
WHERE f.b_user_id = #{userId} AND f.b_friend_id = #{friendId}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { onBeforeUnmount, onMounted } from 'vue'
|
||||
import { initBaseApi } from '@/request/api/base'
|
||||
import { useCommonStore } from '@/store/common'
|
||||
import { getPostTagApi } from './request/api/post'
|
||||
import { listAreasApi, listIndustriesApi } from './request/api/company'
|
||||
import { listPostTagApi } from './request/api/post'
|
||||
import { useWebSocket } from './store/websocket'
|
||||
|
||||
const websocket = useWebSocket()
|
||||
@@ -20,34 +20,43 @@ onBeforeUnmount(() => {
|
||||
|
||||
websocket.close()
|
||||
})
|
||||
async function handleInitData(): Promise<void> {
|
||||
try {
|
||||
// 并行执行三个 API 请求
|
||||
const [areaRes, industryRes, postTagRes] = await Promise.all([
|
||||
listAreasApi(),
|
||||
listIndustriesApi(),
|
||||
listPostTagApi(),
|
||||
])
|
||||
|
||||
function handleInitData() {
|
||||
initBaseApi().then((res) => {
|
||||
if (res.isSuccess) {
|
||||
commonOptions.areaOptions.splice(0, commonOptions.areaOptions.length, ...res.data.areas || [])
|
||||
commonOptions.industryOptions.splice(0, commonOptions.industryOptions.length, ...res.data.industries || [])
|
||||
// 处理区域数据
|
||||
if (areaRes.isSuccess) {
|
||||
commonOptions.setAreaOptions(areaRes.data)
|
||||
}
|
||||
else {
|
||||
ElMessage.error(res.message || '初始化失败,请刷新页面后重试')
|
||||
ElMessage.error(areaRes.message || '初始化失败,请刷新页面后重试')
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
ElMessage.error('初始化失败,请刷新页面后重试')
|
||||
})
|
||||
|
||||
getPostTagApi()
|
||||
.then((res) => {
|
||||
if (res.isSuccess) {
|
||||
commonOptions.postTagOptions.splice(0, commonOptions.postTagOptions.length, ...res.data || [])
|
||||
}
|
||||
else {
|
||||
ElMessage.error(res.message || '初始化失败,请刷新页面后重试')
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err)
|
||||
ElMessage.error('初始化失败,请刷新页面后重试')
|
||||
})
|
||||
// 处理行业数据
|
||||
if (industryRes.isSuccess) {
|
||||
commonOptions.setIndustryOptions(industryRes.data)
|
||||
}
|
||||
else {
|
||||
ElMessage.error(industryRes.message || '初始化失败,请刷新页面后重试')
|
||||
}
|
||||
|
||||
// 处理岗位标签数据
|
||||
if (postTagRes.isSuccess) {
|
||||
commonOptions.setPostTagOptions(postTagRes.data)
|
||||
}
|
||||
else {
|
||||
ElMessage.error(postTagRes.message || '初始化失败,请刷新页面后重试')
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err)
|
||||
ElMessage.error('初始化失败,请刷新页面后重试')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
<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>
|
||||
@@ -0,0 +1,267 @@
|
||||
<script lang="ts" setup>
|
||||
import type { CompanyUserProfile } from '@/types/company/user'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ref } from 'vue'
|
||||
import { getCompanyUserProfileApi } from '@/request/api/company/user'
|
||||
import { createFriendApi } from '@/request/api/im'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import SafeImage from './SafeImage.vue'
|
||||
import SvgIcon from './SvgIcon.vue'
|
||||
|
||||
const props = defineProps({
|
||||
userId: {
|
||||
type: [Number, null],
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const emits = defineEmits<{
|
||||
(e: 'startChat'): void
|
||||
}>()
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const profile = ref<CompanyUserProfile>({
|
||||
company: {
|
||||
id: 0,
|
||||
name: '',
|
||||
logo: '',
|
||||
profile: '',
|
||||
areas: [],
|
||||
industries: [],
|
||||
},
|
||||
user: {
|
||||
id: 0,
|
||||
name: '',
|
||||
position: '',
|
||||
avatar: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
isOnline: false,
|
||||
},
|
||||
})
|
||||
|
||||
function onOpened() {
|
||||
loading.value = true
|
||||
|
||||
if (!props.userId) {
|
||||
return
|
||||
}
|
||||
|
||||
getCompanyUserProfileApi(props.userId).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
profile.value = res.data
|
||||
|
||||
loading.value = false
|
||||
}
|
||||
else {
|
||||
ElMessage.error(res.message || '加载用户信息失败,请稍后再试')
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
ElMessage.error('加载用户信息失败,请稍后再试')
|
||||
})
|
||||
}
|
||||
|
||||
function onClosed() {
|
||||
loading.value = true
|
||||
|
||||
profile.value = {
|
||||
company: {
|
||||
id: 0,
|
||||
name: '',
|
||||
logo: '',
|
||||
profile: '',
|
||||
areas: [],
|
||||
industries: [],
|
||||
},
|
||||
user: {
|
||||
id: 0,
|
||||
name: '',
|
||||
position: '',
|
||||
avatar: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
isOnline: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function handleChat() {
|
||||
if (props.userId === null) {
|
||||
return
|
||||
}
|
||||
|
||||
if (props.userId === userStore.user.id) {
|
||||
ElMessage.warning('无法与自己发起聊天')
|
||||
return
|
||||
}
|
||||
|
||||
// 创建好友关系
|
||||
createFriendApi({
|
||||
friendId: props.userId,
|
||||
}).then((res) => {
|
||||
if (res.isSuccess) {
|
||||
emits('startChat')
|
||||
}
|
||||
else {
|
||||
ElMessage.error(res.message || '发起聊天失败,请稍后再试')
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
ElMessage.error('发起聊天失败,请稍后再试')
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
width="700"
|
||||
class="dlalog-wrapper"
|
||||
@opened="onOpened"
|
||||
@closed="onClosed"
|
||||
>
|
||||
<div v-if="loading" class="loading-wrapper">
|
||||
<SvgIcon icon="svg-spinners:ring-resize" :size="20" />
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="user">
|
||||
<el-badge
|
||||
is-dot
|
||||
:color="profile.user.isOnline ? '#24e68a' : '#bfbfbf'"
|
||||
:badge-style="{
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
}"
|
||||
:offset="[-4, 42]"
|
||||
>
|
||||
<SafeImage class="user-logo" :size="52" round :url="profile.user.avatar" />
|
||||
</el-badge>
|
||||
<div class="user-info">
|
||||
<div class="user-line">
|
||||
<span>{{ profile.user.name }}</span>
|
||||
<span v-if="profile.user.position" class="position"> · {{ profile.user.position }}</span>
|
||||
</div>
|
||||
<div class="user-contract">
|
||||
<div v-if="profile.user.phone" class="user-contract__item">
|
||||
<SvgIcon icon="material-symbols:phone-android-outline" />
|
||||
<span>{{ profile.user.phone }}</span>
|
||||
</div>
|
||||
<div v-if="profile.user.email" class="user-contract__item">
|
||||
<SvgIcon icon="material-symbols:mail-outline" />
|
||||
<span>{{ profile.user.email }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="user-action">
|
||||
<el-button size="small" type="primary" @click="handleChat">
|
||||
发起聊天
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-divider />
|
||||
|
||||
<div class="company">
|
||||
<div class="company-name">
|
||||
{{ profile.company.name }}
|
||||
</div>
|
||||
<div class="company-meta">
|
||||
<span>{{ profile.company.industries.join(" / ") }}</span>
|
||||
|
||||
<span v-if="profile.company.areas.length > 0"> · {{ profile.company.areas[0] }} </span>
|
||||
</div>
|
||||
<div v-if="profile.company.profile" class="company-profile">
|
||||
{{ profile.company.profile }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.dlalog-wrapper {
|
||||
.el-divider {
|
||||
margin: 16px 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-wrapper,
|
||||
.empty-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.empty-wrapper {
|
||||
flex-direction: column;
|
||||
.empty-tip {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.user {
|
||||
display: flex;
|
||||
.user-logo {
|
||||
}
|
||||
|
||||
.user-info {
|
||||
margin-left: 16px;
|
||||
|
||||
.user-line {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #262626;
|
||||
|
||||
.position {
|
||||
font-weight: 400;
|
||||
color: #595959;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-contract {
|
||||
margin-top: 6px;
|
||||
font-size: 13px;
|
||||
color: #595959;
|
||||
gap: 16px;
|
||||
|
||||
.user-contract__item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.user-action {
|
||||
margin-top: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.company {
|
||||
.company-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #262626;
|
||||
}
|
||||
.company-meta {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
color: #8c8c8c;
|
||||
}
|
||||
.company-profile {
|
||||
margin-top: 8px;
|
||||
font-size: 13px;
|
||||
color: #595959;
|
||||
line-height: 1.6;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,13 +1,16 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
CircleClose,
|
||||
User,
|
||||
} from '@element-plus/icons-vue'
|
||||
import { CircleClose, User } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import SafeImage from '@/components/SafeImage.vue'
|
||||
import { getUnreadCountApi } from '@/request/api/im'
|
||||
import { useCountStore } from '@/store/count'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { useWebSocket } from '@/store/websocket'
|
||||
|
||||
const countStore = useCountStore()
|
||||
|
||||
const websocket = useWebSocket()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -18,9 +21,27 @@ const menuList = [
|
||||
{ name: '企业名录', path: '/company' },
|
||||
{ name: '论坛中心', path: '/post' },
|
||||
{ name: '关于我们', path: '/aboutus' },
|
||||
{ name: '联系我们', path: '/forum' },
|
||||
// { name: '联系我们', path: '/forum' },
|
||||
]
|
||||
|
||||
watch(() => userStore.isLogined, (newVal) => {
|
||||
if (newVal) {
|
||||
getUnreadCountApi().then((res) => {
|
||||
if (res.isSuccess) {
|
||||
countStore.setImCount(res.data || 0)
|
||||
}
|
||||
else {
|
||||
ElMessage.error(res.message || '获取IM未读消息数量失败,请稍后再试')
|
||||
}
|
||||
}).catch((err) => {
|
||||
console.log(err)
|
||||
ElMessage.error('获取IM未读消息数量失败,请稍后再试')
|
||||
})
|
||||
}
|
||||
}, {
|
||||
immediate: true,
|
||||
})
|
||||
|
||||
function navigateTo(path: string) {
|
||||
router.push(path)
|
||||
}
|
||||
@@ -58,9 +79,15 @@ function isActive(path: string) {
|
||||
</span>
|
||||
</div>
|
||||
<div class="action">
|
||||
<i-tdesign:search class="action-item" />
|
||||
|
||||
<SvgIcon icon="solar:chat-round-line-linear" class="action-item" @click="navigateTo('/im')" />
|
||||
<!-- <SvgIcon icon="tdesign:search" class="action-item"/> -->
|
||||
<el-badge
|
||||
:value="countStore.imCount"
|
||||
:max="99"
|
||||
:offset="[-22, 0]"
|
||||
:show-zero="false"
|
||||
>
|
||||
<SvgIcon icon="solar:chat-round-line-linear" class="action-item" @click="navigateTo('/im')" />
|
||||
</el-badge>
|
||||
|
||||
<template v-if="userStore.isLogined">
|
||||
<el-divider direction="vertical" />
|
||||
@@ -169,6 +196,10 @@ function isActive(path: string) {
|
||||
flex-shrink: 0;
|
||||
min-width: 0;
|
||||
|
||||
:deep(.el-badge) {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.action-item {
|
||||
font-size: 20px;
|
||||
margin-right: 24px;
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import type { AddFileParams, GetAliyunKeyResult, InitBaseDataResult } from '@/types/base'
|
||||
import type { AddFileParams, GetAliyunKeyResult } from '@/types/base'
|
||||
import { httpGet, httpPostJson } from '@/request/utils'
|
||||
|
||||
export function initBaseApi() {
|
||||
return httpGet<InitBaseDataResult>(`/base/init`)
|
||||
}
|
||||
|
||||
export function getAliyunKeyApi() {
|
||||
return httpGet<GetAliyunKeyResult>(`/base/file/getAliyunKey`)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Company, CompanyDetail, QueryCompanyParams, RecommendCompany, RecommendHotCompany, UpdateCompanyParams } from '@/types/company'
|
||||
import type { Area, Company, CompanyDetail, Industry, QueryCompanyParams, RecommendCompany, RecommendHotCompany, UpdateCompanyParams } from '@/types/company'
|
||||
import { httpGet, httpPostJson } from '@/request/utils'
|
||||
|
||||
export function getRecommendCompanyApi() {
|
||||
@@ -20,3 +20,11 @@ export function getCompanyDetailApi(id: number) {
|
||||
export function updateCompanyApi(params: UpdateCompanyParams) {
|
||||
return httpPostJson<void>(`/company/update`, params)
|
||||
}
|
||||
|
||||
export function listAreasApi() {
|
||||
return httpGet<Area[]>('/company/area')
|
||||
}
|
||||
|
||||
export function listIndustriesApi() {
|
||||
return httpGet<Industry[]>('/company/industry')
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { AddCompanyUserParams, ChatCompanyUser, CompanyUser, DeleteCompanyUserParams, QueryCompanyUserParams, UpdateCompanyUserParams } from '@/types/company/user'
|
||||
import type { AddCompanyUserParams, ChatCompanyUser, CompanyUser, CompanyUserProfile, DeleteCompanyUserParams, QueryCompanyUserParams, UpdateCompanyUserParams } from '@/types/company/user'
|
||||
import { httpGet, httpPostJson } from '@/request/utils'
|
||||
|
||||
export function queryCompanyUserApi(params: QueryCompanyUserParams) {
|
||||
@@ -17,3 +17,7 @@ export function deleteCompanyUserApi(params: DeleteCompanyUserParams) {
|
||||
export function listChatCompanyUsersApi(companyId: number) {
|
||||
return httpGet<ChatCompanyUser[]>(`/company/user/chat/${companyId}`)
|
||||
}
|
||||
|
||||
export function getCompanyUserProfileApi(userId: number) {
|
||||
return httpGet<CompanyUserProfile>(`/company/user/profile/${userId}`)
|
||||
}
|
||||
@@ -39,3 +39,7 @@ export function getFriendProfileApi(friendId: number) {
|
||||
export function getGroupProfileApi(groupId: number) {
|
||||
return httpGet<ImGroupProfile>(`/im/group/${groupId}`)
|
||||
}
|
||||
|
||||
export function getUnreadCountApi() {
|
||||
return httpGet<number>('/im/unreadCount')
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import type { CreatePostCommentParams, CreatePostCommentResult, PostComment, Pos
|
||||
import { httpGet, httpPostJson } from '@/request/utils'
|
||||
|
||||
// ===== 公共接口 =====
|
||||
export function getPostTagApi() {
|
||||
export function listPostTagApi() {
|
||||
return httpGet<PostTag[]>('/post/tag')
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { Area, CompanyQuery, Industry, PostTag } from '@/types/base'
|
||||
import type { CompanyQuery } from '@/types/base'
|
||||
import type { Area, Industry } from '@/types/company'
|
||||
import type { PostTag } from '@/types/post'
|
||||
import { defineStore } from 'pinia'
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
|
||||
@@ -19,11 +21,26 @@ export const useCommonStore = defineStore('common', () => {
|
||||
...postTagOptions,
|
||||
])
|
||||
|
||||
function setAreaOptions(options: Area[]) {
|
||||
areaOptions.splice(0, areaOptions.length, ...options)
|
||||
}
|
||||
|
||||
function setIndustryOptions(options: Industry[]) {
|
||||
industryOptions.splice(0, industryOptions.length, ...options)
|
||||
}
|
||||
|
||||
function setPostTagOptions(options: PostTag[]) {
|
||||
postTagOptions.splice(0, postTagOptions.length, ...options)
|
||||
}
|
||||
|
||||
return {
|
||||
areaOptions,
|
||||
industryOptions,
|
||||
companyQuery,
|
||||
postTagOptions, // 原始数据
|
||||
postTagOptionsWithAll, // 带“全部”,响应式
|
||||
setAreaOptions,
|
||||
setIndustryOptions,
|
||||
setPostTagOptions,
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,25 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export const useCountStore = defineStore('count', () => {
|
||||
const imCount = ref(0)
|
||||
|
||||
function incrementImCount(amount: number = 1) {
|
||||
imCount.value += amount
|
||||
}
|
||||
|
||||
function decrementImCount(amount: number = 1) {
|
||||
imCount.value = Math.max(imCount.value - amount, 0)
|
||||
}
|
||||
|
||||
function setImCount(value: number) {
|
||||
imCount.value = value
|
||||
}
|
||||
|
||||
return {
|
||||
imCount,
|
||||
incrementImCount,
|
||||
decrementImCount,
|
||||
setImCount,
|
||||
}
|
||||
})
|
||||
@@ -16,8 +16,9 @@ declare module 'vue' {
|
||||
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']
|
||||
CompanyUserCard: typeof import('../components/CompanyUserProfileDialog.vue')['default']
|
||||
CompanyUserPopover: typeof import('./../components/CompanyUserPopover.vue')['default']
|
||||
CompanyUserProfileDialog: typeof import('./../components/CompanyUserProfileDialog.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']
|
||||
|
||||
@@ -1,27 +1,3 @@
|
||||
export interface Area {
|
||||
id: string
|
||||
xh: number
|
||||
name: string
|
||||
fatherId: string
|
||||
children: Area[]
|
||||
}
|
||||
|
||||
export interface Industry {
|
||||
id: number
|
||||
xh: number
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface PostTag {
|
||||
id: number
|
||||
name: string
|
||||
icon: string
|
||||
}
|
||||
|
||||
export interface InitBaseDataResult {
|
||||
industries: Industry[]
|
||||
areas: Area[]
|
||||
}
|
||||
|
||||
export interface CompanyQuery {
|
||||
industries: string[]
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
export interface Area {
|
||||
id: string
|
||||
xh: number
|
||||
name: string
|
||||
fatherId: string
|
||||
children: Area[]
|
||||
}
|
||||
|
||||
export interface Industry {
|
||||
id: number
|
||||
xh: number
|
||||
name: string
|
||||
}
|
||||
|
||||
export interface RecommendCompany {
|
||||
shengName: string
|
||||
shiName: string
|
||||
|
||||
@@ -126,12 +126,10 @@ export interface ImGroupMessage {
|
||||
}
|
||||
|
||||
export interface UpdatePrivateUnreadParams {
|
||||
userId: number
|
||||
friendId: number
|
||||
}
|
||||
export interface UpdateGroupUnreadParams {
|
||||
groupId: number
|
||||
userId: number
|
||||
}
|
||||
|
||||
export interface QueryGroupUserParams {
|
||||
@@ -152,6 +150,12 @@ export interface ImFriendProfile {
|
||||
friendName: string
|
||||
friendAvatar: string
|
||||
unreadCount: number
|
||||
|
||||
lastMessageTime: number
|
||||
lastMessageType: number
|
||||
lastMessageContent: string
|
||||
|
||||
isOnline: boolean
|
||||
}
|
||||
|
||||
export interface ImGroupProfile {
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Company } from '@/types/company'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import ChatUserSelect from '@/components/ChatUserSelect.vue'
|
||||
import ChatCompanyUserCard from '@/components/ChatCompanyUserCard.vue'
|
||||
import QueryCompany from '@/components/QueryCompany.vue'
|
||||
import { queryCompanyApi } from '@/request/api/company'
|
||||
import { useCommonStore } from '@/store/common'
|
||||
@@ -111,7 +111,7 @@ function toDetail(data: Company) {
|
||||
</div>
|
||||
</div>
|
||||
<div class="company-actions">
|
||||
<ChatUserSelect :company-id="item.id">
|
||||
<ChatCompanyUserCard :company-id="item.id">
|
||||
<el-button
|
||||
type="primary"
|
||||
>
|
||||
@@ -120,7 +120,7 @@ function toDetail(data: Company) {
|
||||
发起聊天
|
||||
</span>
|
||||
</el-button>
|
||||
</ChatUserSelect>
|
||||
</ChatCompanyUserCard>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -368,7 +368,7 @@ function handleClickGroupUser(groupUser: ImGroupUser) {
|
||||
</div>
|
||||
<div class="im__group-section-header-extra">
|
||||
<div class="im__group-section-header--search">
|
||||
<SvgIcon icon="lucide:search" :size="16" />
|
||||
<!-- <SvgIcon icon="lucide:search" :size="16" /> -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -439,6 +439,7 @@ function handleClickGroupUser(groupUser: ImGroupUser) {
|
||||
width: 100%;
|
||||
.im__chat-message-avatar {
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.im__chat-message-body {
|
||||
width: 100%;
|
||||
|
||||
@@ -384,6 +384,7 @@ function handleClickAvatar(userId: number) {
|
||||
width: 100%;
|
||||
.im__chat-message-avatar {
|
||||
flex-shrink: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
.im__chat-message-body {
|
||||
width: 100%;
|
||||
|
||||
@@ -1,20 +1,31 @@
|
||||
<script lang="ts" setup>
|
||||
import type { ImFriend, ImGroup } from '@/types/im'
|
||||
import type { ImFriend, ImGroup, ImGroupUser } from '@/types/im'
|
||||
import { Search } from '@element-plus/icons-vue'
|
||||
import { computed, nextTick, onMounted, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import CompanyUserProfileDialog from '@/components/CompanyUserProfileDialog.vue'
|
||||
import { getFriendProfileApi, getGroupProfileApi, queryFriendApi, queryGroupApi, updateGroupUnreadApi, updatePrivateUnreadApi } from '@/request/api/im'
|
||||
import { useCountStore } from '@/store/count'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { useWebSocket } from '@/store/websocket'
|
||||
import { MessageType } from '@/types/im'
|
||||
import { formatImTime } from '@/utils/time'
|
||||
|
||||
import GroupChatWindows from './components/GroupChatWindows.vue'
|
||||
import PrivateChatWindow from './components/PrivateChatWindow.vue'
|
||||
|
||||
const countStore = useCountStore()
|
||||
|
||||
const userStore = useUserStore()
|
||||
const route = useRoute()
|
||||
const websocket = useWebSocket()
|
||||
|
||||
// 是否第一次加载
|
||||
const isFirstLoad = ref(true)
|
||||
|
||||
// 公司用户信息
|
||||
const showUserProfile = ref(false)
|
||||
const userProfileId = ref<number | null>(null)
|
||||
|
||||
// 当前聊天对象
|
||||
const searchName = ref('')
|
||||
|
||||
@@ -65,7 +76,9 @@ const groupState = ref<ImListState<ImGroup>>({
|
||||
})
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
isFirstLoad.value = true
|
||||
|
||||
const userId = userStore.user.id
|
||||
|
||||
const type = route.params.type as ChatType | undefined
|
||||
@@ -75,10 +88,12 @@ onMounted(() => {
|
||||
selectedChat.value = { type, id }
|
||||
}
|
||||
|
||||
nextTick(() => {
|
||||
loadGroups()
|
||||
loadFriends()
|
||||
})
|
||||
await Promise.all([
|
||||
loadGroups(),
|
||||
loadFriends(),
|
||||
])
|
||||
|
||||
isFirstLoad.value = false
|
||||
|
||||
websocket.onMessage(async (msg) => {
|
||||
const { type, from, data } = msg
|
||||
@@ -146,6 +161,8 @@ onMounted(() => {
|
||||
// 未读数处理
|
||||
if (userId !== senderId && targetFriendId !== chatFriendId) {
|
||||
friend.unreadCount = (friend.unreadCount || 0) + 1
|
||||
|
||||
countStore.incrementImCount(1)
|
||||
}
|
||||
|
||||
// 当前聊天窗口
|
||||
@@ -157,7 +174,6 @@ onMounted(() => {
|
||||
if (userId !== senderId) {
|
||||
currentFriendChat.value.unreadCount = 0
|
||||
updatePrivateUnreadApi({
|
||||
userId,
|
||||
friendId: chatFriendId!,
|
||||
})
|
||||
}
|
||||
@@ -227,6 +243,7 @@ onMounted(() => {
|
||||
// 4 未读处理(不是自己 & 当前群未打开)
|
||||
if (senderId !== userId && groupId !== currentGroupId) {
|
||||
group.unreadCount = (group.unreadCount || 0) + 1
|
||||
countStore.incrementImCount(1)
|
||||
}
|
||||
|
||||
// 5 当前群窗口处理
|
||||
@@ -240,7 +257,6 @@ onMounted(() => {
|
||||
currentGroupChat.value.unreadCount = 0
|
||||
|
||||
updateGroupUnreadApi({
|
||||
userId,
|
||||
groupId,
|
||||
})
|
||||
}
|
||||
@@ -270,7 +286,7 @@ async function loadFriends() {
|
||||
currentPage: state.page,
|
||||
pageSize: state.pageSize,
|
||||
name: searchName.value,
|
||||
id: isPrivateChat.value ? (selectedChat.value?.id ?? null) : null,
|
||||
id: (isFirstLoad.value && isPrivateChat.value) ? selectedChat.value.id : null,
|
||||
})
|
||||
|
||||
if (!res.isSuccess)
|
||||
@@ -278,15 +294,21 @@ async function loadFriends() {
|
||||
|
||||
const { records, total } = res.data
|
||||
|
||||
state.list
|
||||
= state.page === 1
|
||||
? records
|
||||
: [...state.list, ...records]
|
||||
// 去重:合并新记录并避免重复 friendId
|
||||
const newRecords = records.filter(
|
||||
newFriend => !state.list.some(existingFriend => existingFriend.friendId === newFriend.friendId),
|
||||
)
|
||||
|
||||
// 合并新好友记录到当前列表
|
||||
state.list = state.page === 1
|
||||
? newRecords
|
||||
: [...state.list, ...newRecords]
|
||||
|
||||
state.total = total
|
||||
state.hasMore = state.list.length < total
|
||||
state.page++
|
||||
|
||||
// 如果是私聊,选中的好友移到首位
|
||||
if (isPrivateChat.value && selectedChat.value.id) {
|
||||
const index = state.list.findIndex(
|
||||
item => item.friendId === selectedChat.value.id,
|
||||
@@ -295,7 +317,7 @@ async function loadFriends() {
|
||||
if (index !== -1) {
|
||||
const [target] = state.list.splice(index, 1)
|
||||
|
||||
// 移动到第一位
|
||||
// 将选中的好友移到第一位
|
||||
state.list.unshift(target)
|
||||
|
||||
currentFriendChat.value = target
|
||||
@@ -322,7 +344,7 @@ async function loadGroups() {
|
||||
currentPage: state.page,
|
||||
pageSize: state.pageSize,
|
||||
name: searchName.value,
|
||||
id: isGroupChat.value ? (selectedChat.value?.id ?? null) : null,
|
||||
id: (isFirstLoad.value && isGroupChat.value) ? selectedChat.value?.id : null,
|
||||
})
|
||||
|
||||
if (!res.isSuccess)
|
||||
@@ -330,14 +352,35 @@ async function loadGroups() {
|
||||
|
||||
const { records, total } = res.data
|
||||
|
||||
state.list
|
||||
= state.page === 1
|
||||
? records
|
||||
: [...state.list, ...records]
|
||||
// 去重:合并新记录并避免重复 groupId
|
||||
const newRecords = records.filter(
|
||||
newGroup => !state.list.some(existingGroup => existingGroup.groupId === newGroup.groupId),
|
||||
)
|
||||
|
||||
// 合并新群组记录到当前列表
|
||||
state.list = state.page === 1
|
||||
? newRecords
|
||||
: [...state.list, ...newRecords]
|
||||
|
||||
state.total = total
|
||||
state.hasMore = state.list.length < total
|
||||
state.page++
|
||||
|
||||
// 如果是群组聊天,选中的群组移到首位
|
||||
if (isGroupChat.value && selectedChat.value.id) {
|
||||
const index = state.list.findIndex(
|
||||
item => item.groupId === selectedChat.value.id,
|
||||
)
|
||||
|
||||
if (index !== -1) {
|
||||
const [target] = state.list.splice(index, 1)
|
||||
|
||||
// 将选中的群组移到第一位
|
||||
state.list.unshift(target)
|
||||
|
||||
currentGroupChat.value = target
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
console.error(e)
|
||||
@@ -346,7 +389,49 @@ async function loadGroups() {
|
||||
state.loading = false
|
||||
}
|
||||
}
|
||||
// 好友列表滚动
|
||||
function onFriendListScroll(e: Event) {
|
||||
const target = e.target as HTMLElement
|
||||
|
||||
// 确保 target 是一个有效的 DOM 元素
|
||||
if (!target || !(target instanceof HTMLElement)) {
|
||||
return
|
||||
}
|
||||
|
||||
const totalPages = Math.ceil(friendState.value.total / friendState.value.pageSize)
|
||||
|
||||
// 滚动到底部,加载下一页
|
||||
if (target.scrollHeight - target.scrollTop - target.clientHeight <= 5) {
|
||||
// 确保没有正在加载且页数小于总页数
|
||||
if (friendState.value.page < totalPages && !friendState.value.loading) {
|
||||
friendState.value.page += 1
|
||||
loadFriends()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 群组列表滚动
|
||||
function onGroupListScroll(e: Event) {
|
||||
const target = e.target as HTMLElement
|
||||
|
||||
// 确保 target 是一个有效的 DOM 元素
|
||||
if (!target || !(target instanceof HTMLElement)) {
|
||||
return
|
||||
}
|
||||
|
||||
const totalPages = Math.ceil(groupState.value.total / groupState.value.pageSize)
|
||||
|
||||
// 滚动到底部,加载下一页
|
||||
if (target.scrollHeight - target.scrollTop - target.clientHeight <= 5) {
|
||||
// 确保没有正在加载且页数小于总页数
|
||||
if (groupState.value.page < totalPages && !groupState.value.loading) {
|
||||
groupState.value.page += 1
|
||||
loadGroups()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 重置
|
||||
function resetList<T>(state: ImListState<T>) {
|
||||
state.page = 1
|
||||
state.total = 0
|
||||
@@ -355,6 +440,9 @@ function resetList<T>(state: ImListState<T>) {
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
selectedChat.value.id = null
|
||||
selectedChat.value.type = ''
|
||||
|
||||
resetList(friendState.value)
|
||||
resetList(groupState.value)
|
||||
|
||||
@@ -365,11 +453,6 @@ function handleSearch() {
|
||||
function onPrivateChat(friend: ImFriend) {
|
||||
currentGroupChat.value = null
|
||||
|
||||
const userId = userStore.user.id
|
||||
// 将 friend 的未读置0
|
||||
const hadUnread = friend.unreadCount > 0
|
||||
friend.unreadCount = 0
|
||||
|
||||
// 设置当前聊天
|
||||
selectedChat.value = {
|
||||
type: 'private',
|
||||
@@ -378,9 +461,13 @@ function onPrivateChat(friend: ImFriend) {
|
||||
|
||||
currentFriendChat.value = friend
|
||||
|
||||
if (hadUnread) {
|
||||
// 更新未读数量
|
||||
if (friend.unreadCount > 0) {
|
||||
countStore.decrementImCount(friend.unreadCount)
|
||||
|
||||
friend.unreadCount = 0
|
||||
|
||||
updatePrivateUnreadApi({
|
||||
userId,
|
||||
friendId: friend.friendId,
|
||||
})
|
||||
}
|
||||
@@ -389,12 +476,6 @@ function onPrivateChat(friend: ImFriend) {
|
||||
function onGroupChat(group: ImGroup) {
|
||||
currentFriendChat.value = null
|
||||
|
||||
const userId = userStore.user.id
|
||||
|
||||
// 将 group 的未读置0
|
||||
const hadUnread = group.unreadCount > 0
|
||||
group.unreadCount = 0
|
||||
|
||||
// 设置当前聊天
|
||||
selectedChat.value = {
|
||||
type: 'group',
|
||||
@@ -402,13 +483,106 @@ function onGroupChat(group: ImGroup) {
|
||||
}
|
||||
|
||||
currentGroupChat.value = group
|
||||
if (hadUnread) {
|
||||
|
||||
// 更新未读数量
|
||||
if (group.unreadCount > 0) {
|
||||
countStore.decrementImCount(group.unreadCount)
|
||||
|
||||
group.unreadCount = 0
|
||||
|
||||
updateGroupUnreadApi({
|
||||
groupId: group.groupId,
|
||||
userId,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function handlePrivateAvatarClick(id: number) {
|
||||
userProfileId.value = id
|
||||
|
||||
showUserProfile.value = true
|
||||
}
|
||||
|
||||
function handleGroupAvatarClick(id: number) {
|
||||
userProfileId.value = id
|
||||
|
||||
showUserProfile.value = true
|
||||
}
|
||||
|
||||
function handleGroupUserClick(groupUser: ImGroupUser) {
|
||||
userProfileId.value = groupUser.userId
|
||||
|
||||
showUserProfile.value = true
|
||||
}
|
||||
|
||||
async function handleStartChat() {
|
||||
const friendId = userProfileId.value
|
||||
if (friendId === null) {
|
||||
return
|
||||
}
|
||||
|
||||
// 如果当前聊天已是目标好友,关闭用户资料界面
|
||||
if (selectedChat.value.type === 'private' && selectedChat.value.id === friendId) {
|
||||
userProfileId.value = null
|
||||
showUserProfile.value = false
|
||||
return
|
||||
}
|
||||
|
||||
// 查找是否存在该好友
|
||||
let friend = friendState.value.list.find(item => item.friendId === friendId)
|
||||
|
||||
// 如果好友不存在,拉取好友数据并添加
|
||||
if (!friend) {
|
||||
try {
|
||||
const res = await getFriendProfileApi(friendId)
|
||||
|
||||
friend = {
|
||||
friendId: res.data.friendId,
|
||||
friendAvatar: res.data.friendAvatar,
|
||||
friendName: res.data.friendName,
|
||||
unreadCount: 0,
|
||||
lastMessageTime: res.data.lastMessageTime,
|
||||
lastMessageContent: res.data.lastMessageContent,
|
||||
lastMessageType: res.data.lastMessageType,
|
||||
isOnline: res.data.isOnline,
|
||||
}
|
||||
|
||||
// 更新未读数量
|
||||
if (res.data.unreadCount > 0) {
|
||||
updatePrivateUnreadApi({ friendId })
|
||||
}
|
||||
|
||||
// 将新好友添加到列表首位
|
||||
friendState.value.list.unshift(friend)
|
||||
}
|
||||
catch (e) {
|
||||
console.error('getFriendProfileApi failed', e)
|
||||
return // 出现错误时提前返回
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 如果好友已存在,更新未读消息并将其移到首位
|
||||
if (friend.unreadCount > 0) {
|
||||
friend.unreadCount = 0
|
||||
updatePrivateUnreadApi({ friendId })
|
||||
}
|
||||
|
||||
// 将该好友移到列表首位
|
||||
const index = friendState.value.list.findIndex(item => item.friendId === friendId)
|
||||
if (index !== -1) {
|
||||
friendState.value.list.splice(index, 1)
|
||||
friendState.value.list.unshift(friend)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新选中的聊天为当前好友
|
||||
selectedChat.value = { type: 'private', id: friend.friendId }
|
||||
currentGroupChat.value = null
|
||||
currentFriendChat.value = friend
|
||||
|
||||
// 清除用户资料显示
|
||||
userProfileId.value = null
|
||||
showUserProfile.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -459,7 +633,7 @@ function onGroupChat(group: ImGroup) {
|
||||
单聊
|
||||
</span>
|
||||
</div>
|
||||
<div class="im__list-body">
|
||||
<div class="im__list-body" @scroll="onFriendListScroll">
|
||||
<div
|
||||
v-for="item in friendState.list" :key="item.friendId" class="im__chat-item"
|
||||
:class="{
|
||||
@@ -506,7 +680,7 @@ function onGroupChat(group: ImGroup) {
|
||||
群聊
|
||||
</span>
|
||||
</div>
|
||||
<div class="im__list-body">
|
||||
<div class="im__list-body" @scroll="onGroupListScroll">
|
||||
<div
|
||||
v-for="item in groupState.list"
|
||||
:key="item.groupId"
|
||||
@@ -562,7 +736,7 @@ function onGroupChat(group: ImGroup) {
|
||||
<span> ]</span>
|
||||
</div>
|
||||
<span>
|
||||
公司描述等等等...
|
||||
<!-- 公司描述等等等... -->
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -571,13 +745,15 @@ function onGroupChat(group: ImGroup) {
|
||||
</div>
|
||||
|
||||
<div class="im__window-body">
|
||||
<PrivateChatWindow v-if="isPrivateChat && currentFriendChat != null" :friend="currentFriendChat" />
|
||||
<GroupChatWindows v-if="isGroupChat && currentGroupChat != null" :group="currentGroupChat" />
|
||||
<PrivateChatWindow v-if="isPrivateChat && currentFriendChat != null" :friend="currentFriendChat" @avatar-click="handlePrivateAvatarClick" />
|
||||
<GroupChatWindows v-if="isGroupChat && currentGroupChat != null" :group="currentGroupChat" @avatar-click="handleGroupAvatarClick" @user-click="handleGroupUserClick" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CompanyUserProfileDialog v-model="showUserProfile" :user-id="userProfileId" @start-chat="handleStartChat" />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
import type { PostComment, PublicPostDetail } from '@/types/post'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { nextTick, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import CompanyUserProfileDialog from '@/components/CompanyUserProfileDialog.vue'
|
||||
import SvgIcon from '@/components/SvgIcon.vue'
|
||||
import { createFriendApi } from '@/request/api/im'
|
||||
import { createPostCommentApi, deletePostCommentApi } from '@/request/api/post'
|
||||
import { useUserStore } from '@/store/user'
|
||||
import { formatCommentTime } from '@/utils/time'
|
||||
@@ -17,6 +20,14 @@ const emits = defineEmits<{
|
||||
(e: 'update:modelValue', val: PostComment[]): void
|
||||
}>()
|
||||
|
||||
// 路由
|
||||
const router = useRouter()
|
||||
|
||||
// 公司用户信息
|
||||
const showUserProfile = ref(false)
|
||||
const userProfileId = ref<number | null>(null)
|
||||
|
||||
// 列表信息
|
||||
const commentList = ref<PostComment[]>([])
|
||||
const userStore = useUserStore()
|
||||
|
||||
@@ -200,17 +211,36 @@ function handleDeleteSelfComment(data: PostComment) {
|
||||
// 用户取消删除
|
||||
})
|
||||
}
|
||||
|
||||
// 显示用户信息
|
||||
function handleShowUserProfile(id: number) {
|
||||
userProfileId.value = id
|
||||
showUserProfile.value = true
|
||||
}
|
||||
|
||||
// 点击发起聊天
|
||||
async function handleStartChat() {
|
||||
const friendId = userProfileId.value
|
||||
if (friendId === null) {
|
||||
return
|
||||
}
|
||||
|
||||
userProfileId.value = null
|
||||
showUserProfile.value = false
|
||||
|
||||
router.push(`/im/private/${friendId}`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-for="item in commentList" :key="item.id" class="custom-comment">
|
||||
<div class="custom-comment-left">
|
||||
<div class="custom-comment-left" @click="handleShowUserProfile(item.publishUser.id)">
|
||||
<SafeImage :url="item.publishUser.avatar" :size="32" round />
|
||||
</div>
|
||||
<div class="custom-comment-right">
|
||||
<div class="custom-comment-meta">
|
||||
<div class="custom-comment-title">
|
||||
<div class="custom-comment-author">
|
||||
<div class="custom-comment-author" @click="handleShowUserProfile(item.publishUser.id)">
|
||||
{{ item.publishUser.name }}
|
||||
</div>
|
||||
<!-- <div v-if="item.publishUser.companyId === postDetail.companyId" class="custom-comment-author-tag">
|
||||
@@ -247,13 +277,13 @@ function handleDeleteSelfComment(data: PostComment) {
|
||||
<!-- 子评论 -->
|
||||
<div v-if="item.children && item.children.length > 0" class="custom-comment-children">
|
||||
<div v-for="child in item.children" :key="child.id" class="custom-comment">
|
||||
<div class="custom-comment-left">
|
||||
<div class="custom-comment-left" @click="handleShowUserProfile(child.publishUser.id)">
|
||||
<SafeImage :url="item.publishUser.avatar" :size="32" round />
|
||||
</div>
|
||||
<div class="custom-comment-right">
|
||||
<div class="custom-comment-meta">
|
||||
<div class="custom-comment-title">
|
||||
<div class="custom-comment-author">
|
||||
<div class="custom-comment-author" @click="handleShowUserProfile(child.publishUser.id)">
|
||||
{{ child.publishUser.name }}
|
||||
</div>
|
||||
<!-- <div v-if="child.publishUser.companyId === postDetail.companyId" class="custom-comment-author-tag">
|
||||
@@ -263,7 +293,7 @@ function handleDeleteSelfComment(data: PostComment) {
|
||||
<div style="margin: 0px 8px;">
|
||||
回复
|
||||
</div>
|
||||
<div class="custom-comment-author">
|
||||
<div class="custom-comment-author" @click="handleShowUserProfile(child.replyUser.id)">
|
||||
{{ child.replyUser.name }}
|
||||
</div>
|
||||
</template>
|
||||
@@ -299,6 +329,8 @@ function handleDeleteSelfComment(data: PostComment) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CompanyUserProfileDialog v-model="showUserProfile" :user-id="userProfileId" @start-chat="handleStartChat" />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -317,6 +349,7 @@ function handleDeleteSelfComment(data: PostComment) {
|
||||
.custom-comment-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
cursor: pointer;
|
||||
}
|
||||
.custom-comment-right {
|
||||
display: flex;
|
||||
|
||||
@@ -45,7 +45,18 @@ const postDetail = ref<PublicPostDetail>({
|
||||
|
||||
const postComments = ref<PostComment[]>([])
|
||||
|
||||
const anchorMenu = ref<AnchorMenuItem[]>([])
|
||||
const anchorMenu = ref<AnchorMenuItem[]>([
|
||||
{
|
||||
label: '目录',
|
||||
href: 'anchro',
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
label: '评论',
|
||||
href: 'comment',
|
||||
children: [],
|
||||
},
|
||||
])
|
||||
|
||||
const rootComment = ref<string>('')
|
||||
const rootCommentLoading = ref<boolean>(false)
|
||||
@@ -69,7 +80,6 @@ onMounted(async () => {
|
||||
throw new Error(postRes.message || '加载帖子失败,请稍后再试')
|
||||
}
|
||||
postDetail.value = postRes.data
|
||||
console.log(postDetail.value)
|
||||
|
||||
// 获取公司信息
|
||||
const companyRes = await getPostCompanyById(postDetail.value.companyId)
|
||||
@@ -78,7 +88,6 @@ onMounted(async () => {
|
||||
}
|
||||
|
||||
postCompany.value = companyRes.data
|
||||
console.log(postCompany.value)
|
||||
|
||||
// 获取评论信息
|
||||
const commentRes = await listPostCommentApi(postDetail.value.id)
|
||||
@@ -99,7 +108,7 @@ onMounted(async () => {
|
||||
})
|
||||
|
||||
function handleUpdateEditorDirectory(menus: AnchorMenuItem[]) {
|
||||
anchorMenu.value = anchorMenu.value.concat(menus)
|
||||
anchorMenu.value[0].children = anchorMenu.value[0].children.concat(menus)
|
||||
}
|
||||
|
||||
function handleSubmitRootComment(content: string) {
|
||||
|
||||
Reference in new issue
Block a user