This commit is contained in:
oneao committed 2025-12-18 17:28:06 +08:00
1 parent 1217f7df5f
commit 6e59e8be45
42 files changed
+1256 -430

No files matched your search

@@ -6,6 +6,8 @@ import lombok.NoArgsConstructor;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import java.util.List;
@Data
@AllArgsConstructor
@@ -20,10 +22,10 @@ public class AuthRegisterDTO {
public static class Company {
@NotBlank(message = "公司名称不能为空")
private String name;
@NotBlank(message = "所属行业不能为空")
private String industry;
@NotBlank(message = "公司地址不能为空")
private String area;
@NotEmpty(message = "所属行业不能为空")
private List<String> industries;
@NotEmpty(message = "公司地址不能为空")
private List<String> areas;
private String phone;
private String email;
private String profile;
@@ -43,6 +43,10 @@ public class AuthIdentityService {
}
if(StringUtils.isNotBlank(company.getPhone()) && companyService.isPhoneExists(company.getPhone())){
throw new BusinessException("公司联系方式已被注册");
}
if(StringUtils.isNotBlank(company.getEmail()) && companyService.isEmailExists(company.getEmail())){
throw new BusinessException("公司邮箱已被注册");
}
@@ -60,8 +64,8 @@ public class AuthIdentityService {
new CompanyPO()
.setId(companyId)
.setName(company.getName())
.setIndustry(company.getIndustry())
.setArea(company.getArea())
.setIndustry(StringUtils.join(company.getIndustries(),","))
.setArea(StringUtils.join(company.getAreas(),","))
.setPhone(company.getPhone())
.setEmail(company.getEmail())
.setProfile(company.getProfile())
@@ -116,7 +120,12 @@ public class AuthIdentityService {
*/
private AuthLoginVO buildLoginVO(CompanyUserPO user) {
CompanyPO company = companyService.getById(user.getCompanyId());
AuthToken authToken = tokenService.generateToken(new TokenPayload(user.getCompanyId(), user.getId()));
TokenPayload tokenPayload = new TokenPayload();
tokenPayload.setCompanyId(user.getCompanyId());
tokenPayload.setUserId(user.getId());
AuthToken authToken = tokenService.generateToken(tokenPayload);
return new AuthLoginVO(
company.getId(),
@@ -7,10 +7,7 @@ import cn.link56.common.Result;
import cn.link56.common.utils.HttpUtils;
import cn.link56.common.utils.JsonUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
@@ -20,7 +17,7 @@ public class FileController {
@Resource
private FileService fileService;
@PostMapping("/getAliyunKey")
@GetMapping("/getAliyunKey")
public Result<AliyunKeyVO> getAliyunKey() {
String requestUrl = "http://g3oss.g3soft.cn:8082/upload?orgid=link56&bucket=g3hdzjsj";
String s = HttpUtils.sendGet(requestUrl);
@@ -13,7 +13,7 @@ import lombok.experimental.Accessors;
@Accessors(chain = true)
public class FilePO {
@TableId(value = "b_id")
private String id;
private Long id;
@TableField("mx_filename")
private String name;
@@ -3,6 +3,7 @@ package cn.link56.base.service;
import cn.link56.base.mapper.FileMapper;
import cn.link56.base.model.dto.AddFileDTO;
import cn.link56.base.model.po.FilePO;
import cn.link56.common.utils.snowflake.idgen.IdGenerator;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
@@ -30,7 +31,11 @@ public class FileService extends ServiceImpl<FileMapper, FilePO> {
}
FilePO filePO = new FilePO();
filePO.setExt(suffix).setName(fileName).setMapName(fileMapName).setSize(fileSize);
filePO.setId(IdGenerator.nextId())
.setExt(suffix)
.setName(fileName)
.setMapName(fileMapName)
.setSize(fileSize);
fileMapper.insert(filePO);
}
@@ -30,8 +30,6 @@ public class AuthInterceptor implements HandlerInterceptor {
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
return true;
}
System.out.println(request.getRequestURI());
System.out.println(request.getRequestURL());
try {
String token = request.getHeader("Authorization");
@@ -39,6 +37,12 @@ public class AuthInterceptor implements HandlerInterceptor {
return writeUnauthorized(response);
}
if (token.startsWith("Bearer ")) {
token = token.substring(7);
}else{
return writeUnauthorized(response);
}
boolean flag = authTokenService.validateToken(token);
if (!flag){
@@ -2,17 +2,19 @@ package cn.link56.company.controller;
import cn.link56.common.BasePageVO;
import cn.link56.common.Result;
import cn.link56.company.model.dto.PageCompanyDTO;
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.PageCompanyVO;
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.service.CompanyService;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Positive;
import java.util.List;
@RestController
@@ -23,8 +25,8 @@ public class CompanyController {
// 公司分页
@PostMapping("/query")
public Result<BasePageVO<PageCompanyVO>> query(@RequestBody PageCompanyDTO pageCompanyDTO){
BasePageVO<PageCompanyVO> data = companyService.queryCompany(pageCompanyDTO);
public Result<BasePageVO<QueryCompanyVO>> query(@RequestBody QueryCompanyDTO queryCompanyDTO){
BasePageVO<QueryCompanyVO> data = companyService.queryCompany(queryCompanyDTO);
return Result.success(data);
}
@@ -44,9 +46,19 @@ public class CompanyController {
// 公司详细
@GetMapping("/detail/{id}")
public Result<CompanyDetailVO> getCompanyDetail(@PathVariable Long id){
public Result<CompanyDetailVO> getCompanyDetail(@PathVariable
@NotNull(message = "公司ID不能为空")
@Positive(message = "公司ID不能为空")
Long id){
CompanyDetailVO companyDetailVO = companyService.getCompanyDetail(id);
return Result.success(companyDetailVO);
}
// 更新
@PostMapping("/update")
public Result<Void> updateDetail(@RequestBody @Validated UpdateCompanyDTO updateCompanyDTO){
companyService.updateDetail(updateCompanyDTO);
return Result.success();
}
}
@@ -0,0 +1,39 @@
package cn.link56.company.controller;
import cn.link56.common.BasePageVO;
import cn.link56.common.Result;
import cn.link56.company.model.dto.AddCompanyUserDTO;
import cn.link56.company.model.dto.QueryCompanyUserDTO;
import cn.link56.company.model.dto.UpdateCompanyUserDTO;
import cn.link56.company.model.vo.QueryCompanyUserVO;
import cn.link56.company.service.CompanyUserService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
@RestController
@RequestMapping("/company/user")
public class CompanyUserController {
@Resource
private CompanyUserService companyUserService;
@PostMapping("/query")
public Result<BasePageVO<QueryCompanyUserVO>> getUsersByCompanyId(@RequestBody QueryCompanyUserDTO queryCompanyUserDTO) {
BasePageVO<QueryCompanyUserVO> result = companyUserService.getUsersByCompanyId(queryCompanyUserDTO);
return Result.success(result);
}
@PostMapping("/add")
public Result<Void> addCompanyUser(@RequestBody @Validated AddCompanyUserDTO companyUserDTO){
companyUserService.addCompanyUser(companyUserDTO);
return Result.success();
}
@PostMapping("/update")
public Result<Void> updateCompanyUser(@RequestBody @Validated UpdateCompanyUserDTO companyUserDTO){
companyUserService.updateCompanyUser(companyUserDTO);
return Result.success();
}
}
@@ -0,0 +1,26 @@
package cn.link56.company.model.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AddCompanyUserDTO {
@NotNull(message = "公司不能为空")
private Long companyId;
@NotBlank(message = "邮箱不能为空")
private String email;
@NotBlank(message = "密码不能为空")
private String password;
private String position;
private String name;
private String phone;
private String canuse;
}
@@ -9,7 +9,7 @@ import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageCompanyDTO {
public class QueryCompanyDTO {
private Integer currentPage;
private Integer pageSize;
@@ -0,0 +1,23 @@
package cn.link56.company.model.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotNull;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class QueryCompanyUserDTO {
private Integer currentPage;
private Integer pageSize;
@NotNull(message = "公司ID不能为空")
private Long companyId;
private String name;
private String position;
private String email;
private String phone;
private String canuse;
}
@@ -0,0 +1,34 @@
package cn.link56.company.model.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Date;
import java.util.List;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UpdateCompanyDTO {
@NotNull(message = "公司ID不能为空")
private Long id;
@NotBlank(message = "公司名称不能为空")
private String name;
@NotEmpty(message = "所属行业不能为空")
private List<String> industries;
@NotEmpty(message = "公司地址不能为空")
private List<String> areas;
private Date establishmentDate;
private String logo;
private String phone;
private String email;
private String profile;
private String description;
}
@@ -0,0 +1,28 @@
package cn.link56.company.model.dto;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UpdateCompanyUserDTO {
@NotNull(message = "用户不能为空")
private Long id;
@NotNull(message = "公司不能为空")
private Long companyId;
@NotBlank(message = "邮箱不能为空")
private String email;
@NotBlank(message = "密码不能为空")
private String password;
private String position;
private String name;
private String phone;
private String canuse;
}
@@ -64,4 +64,8 @@ public class CompanyPO {
@TableField("b_hot_recommend")
private String hotRecommend;
@TableField("b_establishment_date")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date establishmentDate;
}
@@ -42,6 +42,9 @@ public class CompanyUserPO {
@TableField("b_position")
private String position;
@TableField("b_canuse")
private String canuse;
@TableField(value = "b_create_time",fill = FieldFill.INSERT)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@@ -4,6 +4,7 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.Date;
import java.util.List;
@Data
@@ -19,6 +20,7 @@ public class CompanyDetailVO {
private String email;
private String phone;
private String description;
private Date establishmentDate;
private List<User> users;
@Data
@@ -0,0 +1,21 @@
package cn.link56.company.model.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class QueryCompanyUserVO {
private Long id;
private String name;
private Integer role;
private String position;
private String email;
private String phone;
private String canuse;
}
@@ -11,7 +11,7 @@ import java.util.List;
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class PageCompanyVO {
public class QueryCompanyVO {
private Long id;
private String name;
private List<String> areas;
@@ -1,26 +1,30 @@
package cn.link56.company.service;
import cn.link56.common.BasePageVO;
import cn.link56.common.context.UserContext;
import cn.link56.common.exception.BusinessException;
import cn.link56.company.mapper.CompanyMapper;
import cn.link56.company.mapper.CompanyUserMapper;
import cn.link56.company.model.dto.PageCompanyDTO;
import cn.link56.company.model.dto.QueryCompanyDTO;
import cn.link56.company.model.dto.UpdateCompanyDTO;
import cn.link56.company.model.middle.RecommendCompanyRow;
import cn.link56.company.model.po.CompanyPO;
import cn.link56.company.model.po.CompanyUserPO;
import cn.link56.company.model.vo.CompanyDetailVO;
import cn.link56.company.model.vo.PageCompanyVO;
import cn.link56.company.model.vo.QueryCompanyVO;
import cn.link56.company.model.vo.RecommendCompanyVO;
import cn.link56.company.model.vo.RecommendHotCompanyVO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
@@ -99,13 +103,24 @@ public class CompanyService extends ServiceImpl<CompanyMapper,CompanyPO> {
return count > 0;
}
public BasePageVO<PageCompanyVO> queryCompany(PageCompanyDTO pageCompanyDTO) {
Integer currentPage = pageCompanyDTO.getCurrentPage();
Integer pageSize = pageCompanyDTO.getPageSize();
/**
* 检查公司邮箱是否重复
*/
public boolean isEmailExists(String email) {
Long count = companyMapper.selectCount(
new LambdaQueryWrapper<CompanyPO>()
.eq(CompanyPO::getEmail, email)
);
return count > 0;
}
String name = pageCompanyDTO.getName();
List<String> areas = pageCompanyDTO.getAreas();
List<String> industries = pageCompanyDTO.getIndustries();
public BasePageVO<QueryCompanyVO> queryCompany(QueryCompanyDTO queryCompanyDTO) {
Integer currentPage = queryCompanyDTO.getCurrentPage();
Integer pageSize = queryCompanyDTO.getPageSize();
String name = queryCompanyDTO.getName();
List<String> areas = queryCompanyDTO.getAreas();
List<String> industries = queryCompanyDTO.getIndustries();
// 分页
Page<CompanyPO> companyPage = new Page<>(currentPage,pageSize);
@@ -136,23 +151,23 @@ public class CompanyService extends ServiceImpl<CompanyMapper,CompanyPO> {
queryWrapper.eq(CompanyPO::getCanuse,"1");
List<PageCompanyVO> result = companyMapper.selectList(companyPage, queryWrapper).stream().map(item -> {
PageCompanyVO pageCompanyVO = new PageCompanyVO();
List<QueryCompanyVO> result = companyMapper.selectList(companyPage, queryWrapper).stream().map(item -> {
QueryCompanyVO queryCompanyVO = new QueryCompanyVO();
pageCompanyVO.setId(item.getId());
pageCompanyVO.setName(item.getName());
pageCompanyVO.setProfile(item.getProfile());
pageCompanyVO.setLogo(item.getLogo());
pageCompanyVO.setAreas(Arrays.stream(item.getArea().split(","))
queryCompanyVO.setId(item.getId());
queryCompanyVO.setName(item.getName());
queryCompanyVO.setProfile(item.getProfile());
queryCompanyVO.setLogo(item.getLogo());
queryCompanyVO.setAreas(Arrays.stream(item.getArea().split(","))
.collect(Collectors.toList()));
pageCompanyVO.setIndustries(Arrays.stream(item.getIndustry().split(","))
queryCompanyVO.setIndustries(Arrays.stream(item.getIndustry().split(","))
.collect(Collectors.toList()));
return pageCompanyVO;
return queryCompanyVO;
}).collect(Collectors.toList());
return new BasePageVO<PageCompanyVO>()
return new BasePageVO<QueryCompanyVO>()
.setRecords(result)
.setTotal(companyPage.getTotal());
}
@@ -172,11 +187,62 @@ public class CompanyService extends ServiceImpl<CompanyMapper,CompanyPO> {
companyDetailVO.setUsers(companyUserPOS.stream()
.map(item -> {
CompanyDetailVO.User user = new CompanyDetailVO.User();
BeanUtils.copyProperties(item, user); // 复制属性
BeanUtils.copyProperties(item, user);
return user;
})
.collect(Collectors.toList()));
return companyDetailVO;
}
@Transactional
public void updateDetail(UpdateCompanyDTO updateCompanyDTO) {
Long id = updateCompanyDTO.getId();
if (!companyUserService.hasPermission(id,UserContext.getUserId())){
throw new BusinessException("您没有权限访问该公司的信息,操作被拒绝");
}
String name = updateCompanyDTO.getName();
String phone = updateCompanyDTO.getPhone();
String email = updateCompanyDTO.getEmail();
Date establishmentDate = updateCompanyDTO.getEstablishmentDate();
String profile = updateCompanyDTO.getProfile();
String description = updateCompanyDTO.getDescription();
String logo = updateCompanyDTO.getLogo();
String area = StringUtils.join(updateCompanyDTO.getAreas(), ",");
String industry = StringUtils.join(updateCompanyDTO.getIndustries(), ",");
// 检查
CompanyPO companyPO = companyMapper.selectById(id);
if (ObjectUtils.isEmpty(companyPO)){
throw new BusinessException("更新失败,该公司不存在");
}
// 检查名称
if (!companyPO.getName().equals(name) && isNameExists(name)){
throw new BusinessException("公司名称已存在");
}
// 检查联系电话
if (!StringUtils.equals(phone, companyPO.getPhone())
&& isPhoneExists(phone)) {
throw new BusinessException("公司联系电话已存在");
}
// 检查邮箱
if (!StringUtils.equals(email, companyPO.getEmail())
&& isEmailExists(email)) {
throw new BusinessException("公司联系邮箱已存在");
}
// 更新
BeanUtils.copyProperties(updateCompanyDTO,companyPO);
companyPO.setArea(area);
companyPO.setIndustry(industry);
// 保存更新后的公司信息
companyMapper.updateById(companyPO);
}
}
@@ -1,13 +1,27 @@
package cn.link56.company.service;
import cn.link56.common.BasePageVO;
import cn.link56.common.context.UserContext;
import cn.link56.common.exception.BusinessException;
import cn.link56.common.utils.snowflake.idgen.IdGenerator;
import cn.link56.company.mapper.CompanyUserMapper;
import cn.link56.company.model.dto.AddCompanyUserDTO;
import cn.link56.company.model.dto.QueryCompanyUserDTO;
import cn.link56.company.model.dto.UpdateCompanyUserDTO;
import cn.link56.company.model.po.CompanyUserPO;
import cn.link56.company.model.vo.QueryCompanyUserVO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class CompanyUserService extends ServiceImpl<CompanyUserMapper, CompanyUserPO> {
@@ -21,6 +35,9 @@ public class CompanyUserService extends ServiceImpl<CompanyUserMapper, CompanyUs
* @return true 表示邮箱已存在,false 表示可用
*/
public boolean isEmailExists(String email) {
if(StringUtils.isBlank(email)){
return false;
}
long count = companyUserMapper.selectCount(
new LambdaQueryWrapper<CompanyUserPO>()
.eq(CompanyUserPO::getEmail, email)
@@ -42,6 +59,38 @@ public class CompanyUserService extends ServiceImpl<CompanyUserMapper, CompanyUs
}
/**
* 校验当前用户是否有权限操作指定的公司
* @param companyId 当前公司ID
* @param userId 当前用户ID
* @return 是否有权限
*/
public boolean hasPermission(Long companyId, Long userId) {
// 根据 userId 查询公司用户信息
CompanyUserPO companyUserPO = companyUserMapper.selectById(userId);
// 如果 companyUserPO 为空,表示该用户不存在,直接返回无权限
if (ObjectUtils.isEmpty(companyUserPO)) {
return false;
}
// 判断用户公司是否属于该公司
if(!UserContext.getCompanyId().equals(companyId)){
return false;
}
// 判断用户是否属于该公司
if (!companyUserPO.getCompanyId().equals(companyId)) {
return false;
}
// 检查用户角色是否为 0,只有角色为 0 才有权限
return companyUserPO.getRole() == 0;
// 如果角色不是 0,表示没有权限
}
public CompanyUserPO loginByEmailAndPassword(String email, String password){
LambdaQueryWrapper<CompanyUserPO> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(CompanyUserPO::getEmail,email)
@@ -63,4 +112,125 @@ public class CompanyUserService extends ServiceImpl<CompanyUserMapper, CompanyUs
return companyUserMapper.selectList(queryWrapper);
}
public BasePageVO<QueryCompanyUserVO> getUsersByCompanyId(QueryCompanyUserDTO queryCompanyUserDTO) {
boolean hasPermission = hasPermission(queryCompanyUserDTO.getCompanyId(), UserContext.getUserId());
if (!hasPermission){
throw new BusinessException("您没有权限访问该公司的信息,操作被拒绝");
}
Integer currentPage = queryCompanyUserDTO.getCurrentPage();
Integer pageSize = queryCompanyUserDTO.getPageSize();
Long companyId = queryCompanyUserDTO.getCompanyId();
String name = queryCompanyUserDTO.getName();
String email = queryCompanyUserDTO.getEmail();
String phone = queryCompanyUserDTO.getPhone();
String position = queryCompanyUserDTO.getPosition();
String canuse = queryCompanyUserDTO.getCanuse();
Page<CompanyUserPO> queryPage = new Page<>(currentPage,pageSize);
LambdaQueryWrapper<CompanyUserPO> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(CompanyUserPO::getCompanyId,companyId);
if (StringUtils.isNotBlank(name)){
queryWrapper.like(CompanyUserPO::getName,name);
}
if (StringUtils.isNotBlank(email)){
queryWrapper.like(CompanyUserPO::getEmail,email);
}
if (StringUtils.isNotBlank(phone)){
queryWrapper.like(CompanyUserPO::getPhone,phone);
}
if (StringUtils.isNotBlank(position)){
queryWrapper.like(CompanyUserPO::getPosition,position);
}
if (StringUtils.isNotBlank(canuse)){
queryWrapper.eq(CompanyUserPO::getCanuse,canuse);
}
List<CompanyUserPO> companyUserPOS = companyUserMapper.selectList(queryPage, queryWrapper);
List<QueryCompanyUserVO> result = companyUserPOS.stream().map(item -> {
QueryCompanyUserVO queryCompanyUserVO = new QueryCompanyUserVO();
BeanUtils.copyProperties(item, queryCompanyUserVO);
return queryCompanyUserVO;
}).collect(Collectors.toList());
return new BasePageVO<>(queryPage.getTotal(),result);
}
@Transactional
public void addCompanyUser(AddCompanyUserDTO companyUserDTO) {
boolean hasPermission = hasPermission(companyUserDTO.getCompanyId(), UserContext.getUserId());
if (!hasPermission){
throw new BusinessException("您没有权限访问该公司的信息,操作被拒绝");
}
String email = companyUserDTO.getEmail();
String phone = companyUserDTO.getPhone();
if(isEmailExists(email)){
throw new BusinessException("邮箱已被注册");
}
if(StringUtils.isNotBlank(phone) && !isPhoneExists(phone)){
throw new BusinessException("手机号已被注册");
}
String canuse = companyUserDTO.getCanuse();
if (StringUtils.isBlank(canuse) || (!"1".equals(canuse) && !"0".equals(canuse))) {
canuse = "1";
}
CompanyUserPO companyUserPO = new CompanyUserPO();
BeanUtils.copyProperties(companyUserDTO,companyUserPO);
companyUserPO.setId(IdGenerator.nextId());
companyUserPO.setRole(1);
companyUserPO.setCanuse(canuse);
companyUserMapper.insert(companyUserPO);
}
@Transactional
public void updateCompanyUser(UpdateCompanyUserDTO companyUserDTO) {
Long id = companyUserDTO.getId();
Long companyId = companyUserDTO.getCompanyId();
String email = companyUserDTO.getEmail();
String phone = companyUserDTO.getPhone();
boolean hasPermission = hasPermission(companyId, UserContext.getUserId());
if (!hasPermission){
throw new BusinessException("您没有权限访问该公司的信息,操作被拒绝");
}
CompanyUserPO companyUserPO = companyUserMapper.selectById(id);
if (ObjectUtils.isEmpty(companyUserPO)){
throw new BusinessException("更新失败,该用户不存在");
}
// 检查邮箱
if (!StringUtils.equals(email, companyUserPO.getEmail())
&& isEmailExists(email)) {
throw new BusinessException("邮箱已被注册");
}
// 检查手机号
if (!StringUtils.equals(phone, companyUserPO.getPhone())
&& isPhoneExists(phone)) {
throw new BusinessException("手机号已被注册");
}
BeanUtils.copyProperties(companyUserDTO,companyUserPO);
companyUserMapper.updateById(companyUserPO);
}
}
@@ -5,43 +5,20 @@ import { computed, useAttrs } from 'vue'
interface Props {
/** 图标名称 */
icon?: string
/** 图标颜色 */
color?: string
/** 图标大小(px) */
fontSize?: number | string
/** 图标大小 */
size?: string | number
}
const props = defineProps<Props>()
const attrs = useAttrs()
const bindAttrs = computed(() => {
const style: Record<string, string> = {}
if (props.color) {
style.color = props.color
}
if (props.fontSize) {
style.fontSize
= typeof props.fontSize === 'number'
? `${props.fontSize}px`
: props.fontSize
}
return {
...attrs,
style: {
...(attrs.style as Record<string, string>),
...style,
},
}
})
const bindAttrs = computed<{ class: string, style: string }>(() => ({
class: (attrs.class as string) || '',
style: (attrs.style as string) || '',
}))
</script>
<template>
<Icon
v-if="icon"
:icon="icon"
v-bind="bindAttrs"
/>
<Icon v-if="props.icon" :icon="props.icon" :width="props.size" :height="props.size" v-bind="bindAttrs" />
</template>
@@ -0,0 +1,28 @@
<script lang="ts" setup>
import type { AnchorMenuItem } from './type'
defineOptions({
name: 'AnchorMenu',
})
defineProps<{
menu: AnchorMenuItem[]
}>()
</script>
<template>
<template v-for="item in menu" :key="item.href">
<el-anchor-link :href="`#${item.href}`">
{{ item.label }}
<!-- 如果有子菜单,递归渲染 -->
<template v-if="item.children && item.children.length > 0" #sub-link>
<AnchorMenu :menu="item.children" />
</template>
</el-anchor-link>
</template>
</template>
<style lang="scss" scoped>
/* 样式部分可以根据需要修改 */
</style>
@@ -0,0 +1,6 @@
export interface AnchorMenuItem {
label: string
href: string
level?: number
children: AnchorMenuItem[]
}
@@ -1,11 +1,11 @@
<script setup lang="ts">
import type { PropType } from 'vue'
import type { AnchorMenuItem } from '@/components/anchor-menu/type'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import { ElMessage } from 'element-plus'
import { computed, nextTick, onBeforeUnmount, onMounted, ref, shallowRef, watch } from 'vue'
import { uploadImage, uploadToAliyun, uploadVideo } from '@/utils/file'
import { registerPreviewMenu, toolbarConfig } from './config'
import Directory from './modules/directory.vue'
import '@wangeditor/editor/dist/css/style.css'
const props = defineProps({
@@ -23,11 +23,29 @@ const props = defineProps({
},
})
const emit = defineEmits(['update:modelValue'])
const emit = defineEmits(['update:modelValue', 'update:directory'])
// 内容 HTML
const valueHtml = ref('')
// Directory
const activeCollapseNames = ref(['1'])
const directory = ref<AnchorMenuItem[]>([])
const updateDirectory = throttle(doGenerateDirectory)
watch(
() => valueHtml.value,
(newContent) => {
if (newContent) {
nextTick(() => {
updateDirectory()
})
}
},
{ immediate: true },
)
// 只读副本
const previewHtml = ref('')
@@ -121,6 +139,74 @@ function handleCreated(editor: any) {
nextTick(() => {})
}
function throttle(fn: any) {
let isRunning = false
return function (...args: any) {
if (!isRunning) {
isRunning = true
requestAnimationFrame(() => {
fn(...args)
isRunning = false
})
}
}
}
function doGenerateDirectory() {
const root = document.querySelector('.editor')
if (!root)
return
// 获取所有 h1, h2, h3 标签
const headings = Array.from(root.querySelectorAll('h1, h2, h3'))
// 用栈来管理当前的父子关系
const toc: AnchorMenuItem[] = []
const stack: AnchorMenuItem[] = []
headings.forEach((heading, idx) => {
if (!heading.id)
heading.id = `section-${idx + 1}`
const level = +heading.tagName[1] // 获取 h1 = 1, h2 = 2, h3 = 3
const item: AnchorMenuItem = {
href: heading.id,
label: heading.textContent || '',
level,
children: [],
}
// 处理层级关系
while (stack.length > 0) {
const top = stack[stack.length - 1] // 获取栈顶元素
if (top.level && top.level >= level) {
stack.pop() // 弹出更高层级的项
}
else {
break // 如果栈顶元素的级别小于当前的级别,停止弹栈
}
}
if (stack.length > 0) {
// 如果栈非空,当前节点为栈顶节点的子节点
stack[stack.length - 1].children.push(item)
}
else {
// 否则,当前节点是顶层节点
toc.push(item)
}
// 将当前节点推入栈中,成为下一个节点的父节点
stack.push(item)
})
// 保存目录结构
directory.value = toc
emit('update:directory', directory.value)
}
</script>
<template>
@@ -129,7 +215,16 @@ function handleCreated(editor: any) {
<Toolbar v-if="props.mode === 'edit'" :class="toolbarClass" class="toolbar" :editor="editorRef" :default-config="toolbarConfig" />
<Editor key="main" v-model="valueHtml" :class="editorClass" class="editor" :default-config="editorConfig" @on-change="handleChange" @on-created="handleCreated" />
</div>
<Directory v-if="showDirecotry" v-model:content="valueHtml" class="directory" />
<div v-if="showDirecotry" class="directory">
<el-collapse v-model="activeCollapseNames">
<el-collapse-item title="目录" name="1">
<el-anchor :offset="70">
<AnchorMenu :menu="directory" />
</el-anchor>
</el-collapse-item>
</el-collapse>
</div>
</div>
<!-- <Preview v-model:visible="showPreview" :content="previewHtml" /> -->
</template>
@@ -168,9 +263,11 @@ function handleCreated(editor: any) {
display: flex;
width: 100%;
height: 100%;
.directory {
width: 200px;
height: 450px;
height: 100%;
margin-left: 16px;
}
.main {
min-width: 0;
@@ -1,176 +0,0 @@
<script setup lang="ts">
import { nextTick, ref, watch } from 'vue'
defineOptions({
name: 'WangEditorDirecotry',
})
const props = defineProps({
content: {
type: String,
},
top: {
type: Number,
},
})
const directory = ref<any[]>([])
const collapsed = ref(false)
function toggleCollapse() {
collapsed.value = !collapsed.value
}
function scrollTo(id: any) {
const el = document.getElementById(id)
if (el) {
if (props.top) {
const rect = el.getBoundingClientRect()
const scrollTop = window.pageYOffset || document.documentElement.scrollTop
const top = rect.top + scrollTop - props.top
window.scrollTo({
top,
behavior: 'smooth',
})
}
else {
el.scrollIntoView({
behavior: 'smooth', // 平滑滚动
block: 'start', // 元素滚动到视口顶部
inline: 'nearest', // 元素在水平方向上的滚动对齐方式
})
}
}
}
function throttle(fn: any) {
let isRunning = false
return function (...args: any) {
if (!isRunning) {
isRunning = true
requestAnimationFrame(() => {
fn(...args)
isRunning = false
})
}
}
}
function doGenerateDirectory() {
const root = document.querySelector('.editor')
if (!root)
return
const headings = Array.from(root.querySelectorAll('h1, h2, h3'))
const toc = headings.map((heading, idx) => {
if (!heading.id)
heading.id = `section-${idx + 1}`
return {
id: heading.id,
text: heading.textContent || '',
level: +heading.tagName[1], // 1 => h1, 2 => h2, 3 => h3
index: idx,
}
})
directory.value = toc
}
const updateDirectory = throttle(doGenerateDirectory)
watch(
() => props.content,
(newContent) => {
if (newContent) {
nextTick(() => {
updateDirectory()
})
}
},
{ immediate: true },
)
</script>
<template>
<div class="directory-card" :class="{ collapsed }">
<div class="directory-header" @click="toggleCollapse">
<div class="directory-header-left">
<i-material-symbols:folder-copy-outline />
<span>目录</span>
</div>
</div>
<div v-show="!collapsed" class="directory-content">
<div v-for="item in directory" :key="item.id" class="directory-item" :class="`level-${item.level}`" @click="scrollTo(item.id)">
{{ item.text }}
</div>
</div>
</div>
</template>
<style scoped lang="scss">
.directory-card {
display: flex;
flex-direction: column;
background-color: #fff;
overflow: hidden;
&.collapsed {
height: 44px !important;
box-shadow: none;
}
}
.directory-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 1rem;
height: 44px;
font-size: 16px;
font-weight: bold;
border-bottom: 1px solid #e0e0e0;
cursor: pointer;
transition: background-color 0.2s ease;
.directory-header-left {
display: flex;
gap: 8px;
align-items: center;
font-size: 14px;
color: #333;
}
}
.directory-content {
flex: 1;
padding: 1rem;
overflow-y: auto;
word-break: break-all;
.directory-item {
padding: 8px 12px;
cursor: pointer;
font-size: 14px;
color: #333;
&.level-1 {
font-weight: 600;
padding-left: 0;
font-size: 16px;
}
&.level-2 {
padding-left: 20px;
font-size: 14px;
}
&.level-3 {
padding-left: 40px;
font-size: 12px;
}
&:hover {
color: #007aff;
}
}
}
</style>
@@ -24,6 +24,8 @@ function navigateTo(path: string) {
function exitLogin() {
userStore.clearUserInfo()
router.push('/auth/login')
}
function isActive(path: string) {
+4 -1
View File
@@ -1,4 +1,5 @@
import ElementPlus from 'element-plus'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import { createApp } from 'vue'
import App from './App.vue'
import { setupRouter } from './router/index'
@@ -11,7 +12,9 @@ async function setupApp() {
setupPinia(app)
setupRouter(app)
app.use(ElementPlus)
app.use(ElementPlus, {
locale: zhCn,
})
app.mount('#app')
}
@@ -1,4 +1,4 @@
import type { CompanyDetail, CompanyListItem, QueryCompanyParams, RecommendCompany, RecommendHotCompany } from '@/types/company'
import type { Company, CompanyDetail, QueryCompanyParams, RecommendCompany, RecommendHotCompany, UpdateCompanyParams } from '@/types/company'
import { httpGet, httpPostJson } from '@/request/utils'
export function getRecommendCompanyApi() {
@@ -10,9 +10,13 @@ export function getRecommendHotCompanyApi() {
}
export function queryCompanyApi(params: QueryCompanyParams) {
return httpPostJson<BasePageResult<CompanyListItem>>('/company/query', params)
return httpPostJson<BasePageResult<Company>>('/company/query', params)
}
export function getCompanyDetailApi(id: number) {
return httpGet<CompanyDetail>(`/company/detail/${id}`)
}
export function updateCompanyApi(params: UpdateCompanyParams) {
return httpPostJson<void>(`/company/update`, params)
}
@@ -0,0 +1,6 @@
import type { CompanyUser, QueryCompanyUserParams } from '@/types/company/user'
import { httpGet, httpPostJson } from '@/request/utils'
export function queryCompanyUserApi(params: QueryCompanyUserParams) {
return httpPostJson<BasePageResult<CompanyUser>>('/company/user/query', params)
}
@@ -30,6 +30,10 @@ const router = createRouter({
path: '/settings/info/company',
component: () => import('@/views/settings/info/company.vue'),
},
{
path: '/settings/info/user',
component: () => import('@/views/settings/info/user.vue'),
},
],
},
],
@@ -23,8 +23,8 @@ export interface LoginApiResult {
export interface RegisterCompanyForm {
name: string
industry: string[]
address: string[]
industries: string[]
areas: string[]
phone: string
email: string
profile: string
@@ -45,8 +45,8 @@ export interface RegisterAdminForm {
export interface RegisterApiParams {
company: {
name: string
industry: string
address: string
industries: string[]
areas: string[]
phone: string
email: string
profile: string
@@ -11,6 +11,7 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
AnchorMenu: typeof import('./../components/anchor-menu/index.vue')['default']
AreaSelect: typeof import('./../components/AreaSelect.vue')['default']
Directory: typeof import('./../components/wang-editor/modules/directory.vue')['default']
HelloWorld: typeof import('./../components/HelloWorld.vue')['default']
@@ -22,6 +23,7 @@ declare module 'vue' {
'IHugeicons:home03': typeof import('~icons/hugeicons/home03')['default']
'IHugeicons:materialAndTexture': typeof import('~icons/hugeicons/material-and-texture')['default']
'IHugeicons:setting07': typeof import('~icons/hugeicons/setting07')['default']
'IHugeicons:upload01': typeof import('~icons/hugeicons/upload01')['default']
'IHugeicons:userSettings01': typeof import('~icons/hugeicons/user-settings01')['default']
'IIc:outlineSettings': typeof import('~icons/ic/outline-settings')['default']
'IIc:twotoneHeadphones': typeof import('~icons/ic/twotone-headphones')['default']
@@ -22,7 +22,7 @@ export interface QueryCompanyParams {
name: string
}
export interface CompanyListItem {
export interface Company {
id: number
name: string
areas: string[]
@@ -43,6 +43,7 @@ export interface CompanyDetail {
phone: string
email: string
description: string
establishmentDate: string
users: {
id: number
companyId: number
@@ -53,3 +54,16 @@ export interface CompanyDetail {
name: string
}[]
}
export interface UpdateCompanyParams {
id: number
name: string
areas: string[]
industries: string[]
establishmentDate: string
profile: string
phone: string
email: string
description: string
logo: string
}
@@ -0,0 +1,21 @@
export interface QueryCompanyUserParams {
currentPage: number
pageSize: number
companyId: number
name: string
position: string
email: string
phone: string
canuse: string
}
export interface CompanyUser {
id: number
role: number
name: string
position: string
email: string
phone: string
canuse: string
}
@@ -1,7 +1,7 @@
<script setup lang="ts">
import type { FormInstance } from 'element-plus'
import type { RegisterCompanyForm } from '@/types/auth'
import { reactive, ref, watch } from 'vue'
import { reactive, ref } from 'vue'
import AreaSelect from '@/components/AreaSelect.vue'
import IndustrySelect from '@/components/IndustrySelect.vue'
import { validateEmail, validatePhone } from '@/utils/validate'
@@ -15,10 +15,10 @@ const formRules = {
name: [
{ required: true, message: '公司名称不能为空', trigger: 'blur' },
],
industry: [
industries: [
{ required: true, message: '所属行业不能为空', trigger: 'blur' },
],
address: [
areas: [
{ required: true, message: '公司地址不能为空', trigger: 'blur' },
],
email: [{ validator: emailValidator, trigger: 'blur' }],
@@ -55,19 +55,13 @@ function phoneValidator(rule: any, value: any, callback: any) {
}
const form = reactive<RegisterCompanyForm>({
name: '',
industry: [],
address: [],
industries: [],
areas: [],
phone: '',
email: '',
profile: '',
})
watch(() => form.industry, (newVal) => {
console.log(newVal)
})
watch(() => form.address, (newVal) => {
console.log(newVal)
})
async function handleSubmit(formEl: FormInstance | undefined) {
if (!formEl)
return
@@ -102,13 +96,13 @@ async function handleSubmit(formEl: FormInstance | undefined) {
</el-col>
<el-col :span="12">
<el-form-item label="所属行业" prop="industry">
<IndustrySelect v-model="form.industry" placeholder="请输入所属行业" />
<IndustrySelect v-model="form.industries" placeholder="请输入所属行业" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="公司地址" prop="address">
<AreaSelect
v-model="form.address"
v-model="form.areas"
placeholder="请输入公司地址"
/>
</el-form-item>
@@ -35,8 +35,8 @@ function handleSubmit() {
registerApi({
company: {
name: props.company.name,
industry: props.company.industry.join(','),
address: props.company.address.join(','),
industries: props.company.industries,
areas: props.company.areas,
phone: props.company.phone,
email: props.company.email,
profile: props.company.profile,
@@ -84,7 +84,7 @@ function handleSubmit() {
<div class="info-item">
<span class="info-label">所属行业</span>
<span class="info-value">
{{ company.industry?.join(',') || '-' }}
{{ company.industries?.join(',') || '-' }}
</span>
</div>
</el-col>
@@ -93,7 +93,7 @@ function handleSubmit() {
<div class="info-item">
<span class="info-label">公司地址</span>
<span class="info-value">
{{ company.address?.join('/') || '-' }}
{{ company.areas?.join('/') || '-' }}
</span>
</div>
</el-col>
@@ -51,8 +51,8 @@ const adminForm = ref<RegisterAdminForm>({
})
const companyForm = ref<RegisterCompanyForm>({
name: '',
industry: [],
address: [],
industries: [],
areas: [],
phone: '',
email: '',
profile: '',
@@ -1,8 +1,11 @@
<script lang="ts" setup>
import type { AnchorMenuItem } from '@/components/anchor-menu/type'
import type { CompanyDetail } from '@/types/company'
import { ElMessage } from 'element-plus'
import { onMounted, ref } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import AnchorMenu from '@/components/anchor-menu/index.vue'
import WangEditor from '@/components/wang-editor/index.vue'
import { getCompanyDetailApi } from '@/request/api/company'
@@ -21,9 +24,34 @@ const companyDetail = ref<CompanyDetail>({
phone: '',
email: '',
description: '',
establishmentDate: '',
users: [],
})
const anchorMenu = ref<AnchorMenuItem[]>([
{
label: '基础信息',
href: 'basic',
children: [],
},
{
label: '公司简介',
href: 'profile',
children: [],
},
{
label: '公司成员',
href: 'users',
children: [],
},
{
label: '公司详细介绍',
href: 'description',
children: [],
},
])
const activeCollapseNames = ref(['1'])
onMounted(() => {
companyId.value = Number(route.query.id) || 0
@@ -45,6 +73,10 @@ onMounted(() => {
ElMessage.error('加载公司信息失败,请稍后再试')
})
})
function handleUpdateEditorDirectory(menus: AnchorMenuItem[]) {
anchorMenu.value[3].children.push(...menus)
}
</script>
<template>
@@ -121,7 +153,7 @@ onMounted(() => {
<div class="info-header">
<span class="info-title">公司详细介绍</span>
</div>
<WangEditor v-model="companyDetail.description" mode="view" />
<WangEditor v-model="companyDetail.description" mode="view" @update:directory="handleUpdateEditorDirectory" />
</div>
</div>
</div>
@@ -131,18 +163,7 @@ onMounted(() => {
导航
</div>
<el-anchor :offset="70">
<el-anchor-link href="#basic">
基础信息
</el-anchor-link>
<el-anchor-link href="#profile">
公司简介
</el-anchor-link>
<el-anchor-link href="#users">
公司成员
</el-anchor-link>
<el-anchor-link href="#description">
公司详细介绍
</el-anchor-link>
<AnchorMenu :menu="anchorMenu" />
</el-anchor>
</el-affix>
</div>
@@ -202,6 +223,18 @@ onMounted(() => {
.info-title {
font-weight: 600;
font-size: 18px;
position: relative;
padding-left: 12px;
}
.info-title::before {
content: '';
position: absolute;
left: 0;
top: 2px;
width: 4px;
height: 25px;
background-color: var(--el-color-primary);
}
}
@@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { CompanyListItem } from '@/types/company'
import type { Company } from '@/types/company'
import { ElMessage } from 'element-plus'
import { onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
@@ -18,7 +18,7 @@ const page = reactive<Pagination>({
const loading = ref<boolean>(true)
const companyList = ref<CompanyListItem[]>([])
const companyList = ref<Company[]>([])
onMounted(() => {
getCompanyList()
@@ -50,7 +50,7 @@ function getCompanyList() {
})
}
function toDetail(data: CompanyListItem) {
function toDetail(data: Company) {
router.push({
path: '/company/detail',
query: {
@@ -137,6 +137,7 @@ function toDetail(data: CompanyListItem) {
layout="prev, pager, next"
background
size="large"
@change="getCompanyList()"
/>
</div>
</div>
@@ -23,7 +23,7 @@ const menuList: NavMenuItem [] = [
},
{
label: '成员设置',
index: '/settings/info/company-user',
index: '/settings/info/user',
icon: 'hugeicons:user-settings-01',
},
],
@@ -1,124 +1,236 @@
<script lang="ts" setup>
import { reactive } from 'vue'
import type { FormInstance } from 'element-plus'
import type { CompanyDetail } from '@/types/company'
import { ElMessage } from 'element-plus'
import { onMounted, reactive, ref } from 'vue'
import { useRouter } from 'vue-router'
import AreaSelect from '@/components/AreaSelect.vue'
import IndustrySelect from '@/components/IndustrySelect.vue'
import SvgIcon from '@/components/SvgIcon.vue'
import WangEditor from '@/components/wang-editor/index.vue'
import { getCompanyDetailApi, updateCompanyApi } from '@/request/api/company'
import { useUserStore } from '@/store/user'
import { uploadImage, uploadToAliyun } from '@/utils/file'
import { validateEmail, validatePhone } from '@/utils/validate'
const form = reactive({
name: '',
region: '',
date1: '',
date2: '',
delivery: false,
type: [],
resource: '',
desc: '',
const router = useRouter()
const userStore = useUserStore()
const loading = reactive({
save: false,
})
const formRef = ref<FormInstance>()
const formRules = {
name: [
{ required: true, message: '公司名称不能为空', trigger: 'blur' },
],
industries: [
{ required: true, message: '所属行业不能为空', trigger: 'blur' },
],
areas: [
{ required: true, message: '公司地址不能为空', trigger: 'blur' },
],
email: [{ validator: emailValidator, trigger: 'blur' }],
phone: [{ validator: phoneValidator, trigger: 'blur' }],
}
function emailValidator(rule: any, value: any, callback: any) {
// 未填写:直接通过
if (!value) {
callback()
return
}
if (!validateEmail(value)) {
callback(new Error('邮箱格式不正确'))
}
else {
callback()
}
}
function phoneValidator(rule: any, value: any, callback: any) {
// 未填写:直接通过
if (!value) {
callback()
return
}
if (!validatePhone(value)) {
callback(new Error('手机号格式不正确'))
}
else {
callback()
}
}
const companyDetail = ref<CompanyDetail>({
id: 0,
name: '',
areas: [],
industries: [],
logo: '',
profile: '',
phone: '',
email: '',
description: '',
establishmentDate: '',
users: [],
})
onMounted(() => {
getCompanyDetail()
})
function getCompanyDetail() {
getCompanyDetailApi(userStore.userInfo.company.id).then((res) => {
if (res.isSuccess) {
companyDetail.value = res.data
}
else {
ElMessage.error(res.message || '加载公司信息失败,请稍后再试')
router.push('/')
}
}).catch((err) => {
console.log(err)
ElMessage.error('加载公司信息失败,请稍后再试')
})
}
function handleUploadLogo() {
uploadImage().then((data) => {
uploadToAliyun(data.blob, data.name, data.size).then((res) => {
companyDetail.value.logo = res.url
})
})
}
async function handleSubmit(formEl: FormInstance | undefined) {
if (!formEl)
return
await formEl.validate((valid) => {
if (valid) {
loading.save = true
updateCompanyApi({
id: companyDetail.value.id,
name: companyDetail.value.name,
areas: companyDetail.value.areas,
industries: companyDetail.value.industries,
establishmentDate: companyDetail.value.establishmentDate,
profile: companyDetail.value.profile,
phone: companyDetail.value.phone,
email: companyDetail.value.email,
description: companyDetail.value.description,
logo: companyDetail.value.logo,
}).then((res) => {
if (res.isSuccess) {
userStore.userInfo.company.name = companyDetail.value.name
userStore.userInfo.company.logo = companyDetail.value.logo
ElMessage.success('更新成功')
}
else {
ElMessage.error(res.message || '更新失败,请稍后再试')
}
}).catch((err) => {
console.log(err)
ElMessage.error('更新失败,请稍后再试')
}).finally(() => {
loading.save = false
})
}
})
}
const activeCollapseNames = ref(['1', '2', '3'])
</script>
<template>
<div class="company-wrapper">
<div class="form-item">
<div class="form-header">
<span class="form-title">
基础信息
</span>
</div>
<el-collapse v-model="activeCollapseNames" expand-icon-position="right">
<el-collapse-item title="基础信息" name="1">
<el-form
ref="formRef"
:model="companyDetail"
:rules="formRules"
label-width="auto"
label-position="top"
>
<el-row :gutter="16">
<el-col :span="24">
<el-form-item>
<el-avatar :size="86" :src="companyDetail.logo || '/no-avatar.png'" shape="square" />
<div class="logo-upload">
<el-button type="primary" @click="handleUploadLogo()">
<SvgIcon icon="majesticons:cloud-upload-line" :size="18" style="margin-right: 4px;" />
上传LOGO
</el-button>
<span>支持 JPG、PNG 格式,文件大小不超过 2MB</span>
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="公司名称" prop="name">
<el-input v-model="companyDetail.name" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属行业" prop="industries">
<IndustrySelect v-model="companyDetail.industries" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="成立时间" prop="establishmentDate">
<el-date-picker
v-model="companyDetail.establishmentDate"
type="date"
placeholder="请选择公司成立时间"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="公司地址" prop="areas">
<AreaSelect v-model="companyDetail.areas" />
</el-form-item>
</el-col>
<el-form
:model="form"
label-width="auto"
label-position="top"
>
<el-row :gutter="16">
<el-col :span="24">
<el-form-item>
<el-avatar :size="86" shape="square" />
<div class="logo-upload">
<el-button type="primary" plain>
上传LOGO
</el-button>
<span>支持 JPG、PNG 格式,文件大小不超过 2MB</span>
</div>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="公司名称">
<el-input v-model="form.name" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所属行业">
<el-select v-model="form.region" placeholder="please select your zone">
<el-option label="Zone one" value="shanghai" />
<el-option label="Zone two" value="beijing" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="成立时间">
<el-date-picker
v-model="form.date1"
type="date"
placeholder="Pick a date"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="员工规模">
<el-input v-model="form.desc" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="公司地址">
<el-input v-model="form.name" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="公司详细地址">
<el-input v-model="form.desc" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系电话">
<el-input v-model="form.name" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系邮箱">
<el-input v-model="form.desc" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</div>
<div class="form-item">
<div class="form-header">
<span class="form-title">
公司简介
</span>
</div>
<el-form :model="form" label-width="auto">
<el-form-item>
<el-input v-model="form.name" type="textarea" />
</el-form-item>
</el-form>
</div>
<div class="form-item">
<div class="form-header">
<span class="form-title">
公司介绍
</span>
</div>
<WangEditor />
</div>
<el-col :span="12">
<el-form-item label="联系电话" prop="phone">
<el-input v-model="companyDetail.phone" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系邮箱" prop="email">
<el-input v-model="companyDetail.email" />
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-collapse-item>
<el-collapse-item title="公司简介" name="2">
<el-form
ref="formRef"
:model="companyDetail"
:rules="formRules"
label-width="auto"
>
<el-form-item prop="profile">
<el-input v-model="companyDetail.profile" type="textarea" />
</el-form-item>
</el-form>
</el-collapse-item>
<el-collapse-item title="公司介绍" name="3">
<WangEditor v-model="companyDetail.description" show-direcotry />
</el-collapse-item>
</el-collapse>
<div class="form-footer">
<el-button>重置</el-button>
<el-button type="primary">
<el-button @click="getCompanyDetail()">
<SvgIcon icon="ic:baseline-refresh" :size="18" style="margin-right: 4px;" />
重置
</el-button>
<el-button type="primary" :loading="loading.save" @click="handleSubmit(formRef)">
<SvgIcon icon="majesticons:save-line" :size="18" style="margin-right: 4px;" />
保存
</el-button>
</div>
@@ -129,33 +241,37 @@ const form = reactive({
.company-wrapper {
padding: 24px 32px;
.form-item {
margin-bottom: 16px;
.form-header {
margin-bottom: 16px;
.form-title {
font-size: 18px;
font-weight: 600;
}
}
.logo-upload {
margin-left: 16px;
display: flex;
flex-direction: column;
span {
font-size: 14px;
color: #666;
}
}
:deep(.el-collapse) {
border: none;
}
:deep(.el-collapse-item__wrap) {
border-bottom: none;
}
:deep(.el-collapse-item__header){
font-size: 18px;
font-weight: 600;
}
:deep(.el-collapse-item__content){
padding-bottom: 0px;
}
.logo-upload {
margin-left: 16px;
display: flex;
flex-direction: column;
span {
font-size: 14px;
color: #666;
}
}
.form-footer {
margin-top: 16px;
width: 100%;
display: flex;
justify-content: flex-end;
:deep(.el-button){
:deep(.el-button) {
width: 120px;
}
}
@@ -0,0 +1,249 @@
<script lang="ts" setup>
import type { CompanyUser, QueryCompanyUserParams } from '@/types/company/user'
import { Delete, Plus, Refresh, Search } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { onMounted, reactive, ref } from 'vue'
import { queryCompanyUserApi } from '@/request/api/company/user'
import { useUserStore } from '@/store/user'
const userStore = useUserStore()
const activeCollapseNames = ref(['1'])
const page = reactive<Pagination>({
current: 1,
pageSize: 10,
total: 0,
})
const queryForm = reactive<QueryCompanyUserParams>({
currentPage: page.current,
pageSize: page.pageSize,
companyId: userStore.userInfo.company.id,
name: '',
phone: '',
position: '',
email: '',
canuse: '',
})
const canuseOptions = [
{
value: '',
label: '全部',
},
{
value: '1',
label: '启用',
},
{
value: '0',
label: '禁用',
},
]
const tableData = ref<CompanyUser[]>([])
onMounted(() => {
getCompanyUser()
})
function getCompanyUser() {
queryCompanyUserApi({
...queryForm,
currentPage: page.current,
pageSize: page.pageSize,
}).then((res) => {
if (res.isSuccess) {
tableData.value = res.data.records
page.total = res.data.total
}
else {
ElMessage.error(res.message || '查询失败,请稍后再试')
}
}).catch((err) => {
console.log(err)
ElMessage.error('查询失败,请稍后再试')
})
}
</script>
<template>
<div class="users-wrapper">
<el-collapse v-model="activeCollapseNames">
<el-collapse-item title="搜索条件" name="1">
<el-form :model="queryForm">
<el-row :gutter="16">
<el-col :span="6">
<el-form-item label="名称">
<el-input v-model="queryForm.name" placeholder="名称" clearable />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="职位">
<el-input v-model="queryForm.position" placeholder="职位" clearable />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="邮箱">
<el-input v-model="queryForm.email" placeholder="邮箱" clearable />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="手机">
<el-input v-model="queryForm.phone" placeholder="手机" clearable />
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="状态">
<el-select v-model="queryForm.canuse">
<el-option
v-for="item in canuseOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="18">
<el-form-item>
<el-button :icon="Refresh">
重置
</el-button>
<el-button type="primary" :icon="Search">
查询
</el-button>
</el-form-item>
</el-col>
</el-row>
</el-form>
</el-collapse-item>
</el-collapse>
<div class="table-container">
<div class="table-header">
<div class="table-title">
成员列表
</div>
<div class="table-actions">
<el-button type="primary" plain :icon="Plus">
新增
</el-button>
<el-button type="danger" plain :icon="Delete">
批量删除
</el-button>
<el-button :icon="Refresh">
刷新
</el-button>
</div>
</div>
<div class="table-body-wrapper">
<el-table :data="tableData" border stripe>
<el-table-column type="selection" width="55" align="center" />
<el-table-column prop="name" label="名称" align="center" />
<el-table-column prop="position" label="职位" align="center" />
<el-table-column prop="email" label="邮箱" align="center" />
<el-table-column prop="phone" label="电话" align="center" />
<el-table-column prop="canuse" label="启用" width="100" align="center">
<template #default="scope">
<el-tag :type="scope.row.canuse === '1' ? 'primary' : 'warning'">
启用
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" fixed="right">
<template #default="scope">
<el-button size="small">
修改
</el-button>
<el-button
v-if="scope.row.role !== 0"
size="small"
type="danger"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div class="table-pagination">
<el-pagination
v-model:current-page="page.current"
:page-size="page.pageSize"
:total="page.total"
layout="prev, pager, next"
background
size="large"
@change="getCompanyUser()"
/>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.users-wrapper {
display: flex;
flex-direction: column;
height: 100%;
padding: 16px 32px 0px 32px;
:deep(.el-collapse) {
border: none;
}
:deep(.el-collapse-item__wrap) {
border-bottom: none;
}
:deep(.el-collapse-item__header) {
font-size: 18px;
font-weight: 500;
}
:deep(.el-collapse-item__content) {
padding-bottom: 0px;
}
.table-container {
flex: 1;
margin-top: 16px;
display: flex;
flex-direction: column;
min-height: 0;
.table-body-wrapper {
flex: 1;
overflow: hidden;
min-height: 0;
:deep(.el-table) {
height: 100%;
}
}
.table-header {
display: flex;
align-items: center;
justify-content: space-between;
height: 56px;
.table-title {
font-size: 18px;
font-weight: 500;
}
}
:deep(.el-table__header) {
.el-table__cell {
background-color: #f5f7fa;
color: #000;
}
}
.table-pagination {
height: 56px;
width: 100%;
display: flex;
justify-content: flex-end;
}
}
}
</style>