36 Commits

Author SHA1 Message Date
zhuoda
e75c1ec34d 【V3.2.0】1、左侧菜单Logo和标题固定;2、Excel导出添加水印;3、长时间不在线自动返回登录页;4、移除sa-token的 token-prefix 配置;5、升级 ant deign vue到最新版4.2;6、登录页面引入登录类型图标方式;7、文件预览组件的文件下载方式为接口方式下载; 2024-04-22 23:27:30 +08:00
1024创新实验室
51aa2e08ba !29 为导出Excel导出添加水印
Merge pull request !29 from BOOM/master
2024-04-22 14:41:20 +00:00
刘哲贤
18cd04c33a 封装excel导出类,简化Controller中代码 2024-04-16 22:46:13 +08:00
刘哲贤
48d108d4e6 实现easyexcel中的SheetWriteHandler,在afterSheetCreate中添加水印 2024-04-16 22:43:43 +08:00
刘哲贤
ce8db0389e 新增水印配置类 2024-04-16 22:42:57 +08:00
刘哲贤
44938b34a5 添加导出是否带水印配置,默认为带水印 2024-04-16 22:42:20 +08:00
刘哲贤
c761a94c43 添加ooxml-schemas依赖,实现导出带水印 2024-04-16 22:41:51 +08:00
1024创新实验室
ad8ae5bb0f !28 部分细节优化
Merge pull request !28 from 大熊/master
2024-04-16 13:39:23 +00:00
Zhou Mingfa
52a7a5f839 解决登录页面的登录类型图标不显示问题与背景图片不显示问题 2024-04-11 20:32:14 +08:00
Zhou Mingfa
a90b8d114a 代码格式化与部分细节优化 2024-04-11 20:26:00 +08:00
Zhou Mingfa
9100dc1225 修改文件预览组件的文件下载方式为接口方式下载,解决了下载时整个页面闪烁的问题;优化相册样式。 2024-04-11 20:24:59 +08:00
Zhou Mingfa
2e0cb6a96d 在获取通知公告详情更新表单中添加分类名称,用于前端通知公告详情页面中显示分类名称 2024-04-11 16:40:38 +08:00
Zhou Mingfa
af4c470e93 删除发票信息的接口路径错误 2024-04-11 15:59:55 +08:00
1024创新实验室
71521d1d41 !26 后端员工通知公告查询Bug修复以及前端文件上传组件优化
Merge pull request !26 from 大熊/master
2024-04-10 11:40:13 +00:00
1024创新实验室
8558fd307b !27 前端代码:为help-doc的详情页面 提供好看的html样式
Merge pull request !27 from tsukimojo/master
2024-04-10 11:37:23 +00:00
good
d155e8e2cd 前端代码:为help-doc的详情页面 提供好看的html样式(标题、表格等) 2024-04-10 13:35:38 +08:00
卓大
abcbc7fda6 update README.md.
Signed-off-by: 卓大 <zhuoluodada@qq.com>
2024-04-06 14:18:28 +00:00
zhuoda
2713ecac0b v3.1.0 1、【新增】9种登录背景图和样式; 2、【新增】全局字体大小切换; 3、【新增】主题颜色切换; 4、【新增】移除cookie保存token,改为使用localStorage; 5、【优化】升级 ant design vue 到最新版本; 2024-04-06 21:02:51 +08:00
zhuoda
6a2c86d9f2 v3.1.0 1、【新增】9种登录背景图和样式; 2、【新增】全局字体大小切换; 3、【新增】主题颜色切换; 4、【新增】移除cookie保存token,改为使用localStorage; 5、【优化】升级 ant design vue 到最新版本; 2024-04-06 21:01:43 +08:00
大熊
d4ae31cad0 Merge branch 'master' of gitee.com:lab1024/smart-admin into master
Signed-off-by: 大熊 <daxiongren@foxmail.com>
2024-03-20 07:51:46 +00:00
卓大
994b3954ed update README.md.
Signed-off-by: 卓大 <zhuoluodada@qq.com>
2024-03-18 15:20:08 +00:00
卓大
cb9f1f22e6 update README.md.
Signed-off-by: 卓大 <zhuoluodada@qq.com>
2024-03-18 15:17:58 +00:00
zhuoda
1723f2514f 【v3.0.1】1、【新增】tomcat访问日志accessslog 最大保存天数;2、【新增】获取登录结果信息接口返回 Token;3、【新增】员工列表、表单 新增超级管理员标识;4、【优化】移除dev环境swagger用户名和密码;5、【优化】成功返回消息内容改为"操作成功";6、【优化】代码下载zip文件名改为 下划线,eg:t_apply_code.zip;7、【优化】Service代码生成自动Import;8、【优化】AdminInterpter 拦截器中对超级管理员放行;9、【优化】优化重复提交RepeatSubmitAspect;10、【优化】修改其他bug 2024-03-18 22:36:36 +08:00
zhuoda
27dffd9919 【smart-app更新】1、意见反馈;2、我的;3、退出登录;4、等等其他 2024-03-17 22:53:19 +08:00
zhuoda
83d316a2d1 【smart-app更新】1、意见反馈;2、我的;3、退出登录;4、等等其他 2024-03-17 22:52:00 +08:00
zhuoda
4b36de6de5 【smart-app更新】1、版本更新记录;2、复杂表单‘3、引入tabs组件 2024-03-15 22:43:39 +08:00
zhuoda
d170a9d189 【smart-app更新】1、版本更新记录;2、复杂表单‘3、引入tabs组件 2024-03-13 21:05:28 +08:00
zhuoda
3b31558adb Merge branch 'master' of https://gitee.com/lab1024/smart-admin 2024-03-10 22:26:57 +08:00
zhuoda
728ddb9a7e smart-app alpha 版本 2024-03-10 22:26:32 +08:00
Zhou Mingfa
e1f5b5ca12 解决查看更新日志的 modal 对话框点击右上角的叉不能关闭的问题 2024-02-23 09:55:28 +08:00
Zhou Mingfa
47dd346f14 变量命名错别字 2024-02-23 09:51:17 +08:00
Zhou Mingfa
1812cb3d6b 文件上传组件:解决不能点击下载文件的问题、支持多选文件上传、新增超出文件最大数量与超出单文件大小相应的错误提示。 2024-02-22 17:26:19 +08:00
Zhou Mingfa
18c1ac5a5b 修复 防止重复提交注解 不生效的问题 2024-02-22 15:54:51 +08:00
Zhou Mingfa
28834e2515 解决员工查询不到未读通知公告的问题 2024-02-22 11:28:18 +08:00
Zhou Mingfa
caab0040ff 解决员工通知公告按关键字查询时的SQL报错 2024-02-22 11:20:03 +08:00
kkaiyun
2c72327c8e 删除参数保持一致 2024-01-29 09:15:01 +08:00
379 changed files with 24680 additions and 6853 deletions

View File

@@ -8,10 +8,11 @@
### **技术体系**
- 前端Vue3 + Vite5 + Vue-Router + Pinia + Ant Design Vue 4.X
- 移动端uniapp + uview2.x
- 移动端uniapp (vue3版本) + uni-ui + 同时支持APP、小程序、H5
- 后端SpringBoot + Sa Token + Mybatis-plus + 多种数据库
- 在线预览:[https://preview.smartadmin.vip](https://preview.smartadmin.vip)
- 电脑在线预览:[https://preview.smartadmin.vip](https://preview.smartadmin.vip)
- 官方文档:[https://smartadmin.vip](https://smartadmin.vip)
- 移动端在线预览:[https://app.smartadmin.vip](https://app.smartadmin.vip/#/pages/login/login)
### **理念与思想**
- 我们分享的不是徒劳无功的各种功能,而是必须有的功能,如:网络安全、数据变动记录、系统说明文档、版本更新记录、意见反馈、日志、心跳、单号生成器等等。

View File

@@ -39,6 +39,7 @@
<xerces.version>2.12.0</xerces.version>
<easy-excel.version>3.3.2</easy-excel.version>
<poi.version>5.2.4</poi.version>
<ooxml-schemas.version>1.4</ooxml-schemas.version>
<aws-java-sdk.version>1.11.842</aws-java-sdk.version>
<log4j-spring-boot.version>2.17.2</log4j-spring-boot.version>
<hutool.version>5.7.22</hutool.version>
@@ -288,6 +289,12 @@
<version>${poi.version}</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>ooxml-schemas</artifactId>
<version>${ooxml-schemas.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>

View File

@@ -109,6 +109,11 @@ public class AdminInterceptor implements HandlerInterceptor {
return true;
}
// 如果是超级管理员的话,不需要校验权限
if(requestEmployee.getAdministratorFlag()){
return true;
}
SaStrategy.instance.checkMethodAnnotation.accept(method);
} catch (SaTokenException e) {

View File

@@ -1,9 +1,8 @@
package net.lab1024.sa.admin.module.business.goods.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.alibaba.excel.EasyExcel;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import net.lab1024.sa.admin.constant.AdminSwaggerTagConst;
import net.lab1024.sa.admin.module.business.goods.domain.form.GoodsAddForm;
import net.lab1024.sa.admin.module.business.goods.domain.form.GoodsQueryForm;
@@ -14,7 +13,7 @@ import net.lab1024.sa.admin.module.business.goods.service.GoodsService;
import net.lab1024.sa.base.common.domain.PageResult;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.domain.ValidateList;
import net.lab1024.sa.base.common.util.SmartResponseUtil;
import net.lab1024.sa.base.common.util.SmartExcelUtil;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
@@ -88,17 +87,8 @@ public class GoodsController {
@GetMapping("/goods/exportGoods")
@SaCheckPermission("goods:exportGoods")
public void exportGoods(HttpServletResponse response) throws IOException {
List<GoodsExcelVO> goodsList = goodsService.getAllGoods();
// 设置下载消息头
SmartResponseUtil.setDownloadFileHeader(response, "商品列表.xls", null);
// 下载
EasyExcel.write(response.getOutputStream(), GoodsExcelVO.class)
.autoCloseStream(Boolean.FALSE)
.sheet("商品")
.doWrite(goodsList);
SmartExcelUtil.exportExcel(response,"商品列表.xlsx","商品",GoodsExcelVO.class, goodsList);
}
}

View File

@@ -1,7 +1,6 @@
package net.lab1024.sa.admin.module.business.oa.enterprise;
import cn.dev33.satoken.annotation.SaCheckPermission;
import com.alibaba.excel.EasyExcel;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
@@ -11,11 +10,11 @@ import net.lab1024.sa.admin.module.business.oa.enterprise.domain.vo.EnterpriseEm
import net.lab1024.sa.admin.module.business.oa.enterprise.domain.vo.EnterpriseExcelVO;
import net.lab1024.sa.admin.module.business.oa.enterprise.domain.vo.EnterpriseListVO;
import net.lab1024.sa.admin.module.business.oa.enterprise.domain.vo.EnterpriseVO;
import net.lab1024.sa.admin.util.AdminRequestUtil;
import net.lab1024.sa.base.common.domain.PageResult;
import net.lab1024.sa.base.common.domain.RequestUser;
import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.util.SmartRequestUtil;
import net.lab1024.sa.base.common.util.SmartResponseUtil;
import net.lab1024.sa.base.common.util.*;
import net.lab1024.sa.base.module.support.operatelog.annotation.OperateLog;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.web.bind.annotation.*;
@@ -24,6 +23,7 @@ import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.List;
/**
@@ -60,14 +60,11 @@ public class EnterpriseController {
return;
}
// 设置下载消息头
SmartResponseUtil.setDownloadFileHeader(response, "企业基本信息.xls", null);
String watermark = AdminRequestUtil.getRequestUser().getActualName();
watermark += SmartLocalDateUtil.format(LocalDateTime.now(), SmartDateFormatterEnum.YMD_HMS);
SmartExcelUtil.exportExcelWithWatermark(response,"企业基本信息.xlsx","企业信息",EnterpriseExcelVO.class,data,watermark);
// 下载
EasyExcel.write(response.getOutputStream(), EnterpriseExcelVO.class)
.autoCloseStream(Boolean.FALSE)
.sheet("企业信息")
.doWrite(data);
}
@Operation(summary = "查询企业详情 @author 开云")

View File

@@ -44,11 +44,9 @@ public class NoticeVO {
private LocalDateTime publishTime;
@Schema(description = "作者")
@NotBlank(message = "作者不能为空")
private String author;
@Schema(description = "来源")
@NotBlank(message = "标题不能为空")
private String source;
@Schema(description = "文号")

View File

@@ -1,5 +1,6 @@
package net.lab1024.sa.admin.module.system.login.controller;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.extra.servlet.ServletUtil;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
@@ -47,7 +48,10 @@ public class LoginController {
@GetMapping("/login/getLoginInfo")
@Operation(summary = "获取登录结果信息 @author 卓大")
public ResponseDTO<LoginResultVO> getLoginInfo() {
return ResponseDTO.ok(loginService.getLoginResult(AdminRequestUtil.getRequestUser()));
LoginResultVO loginResult = loginService.getLoginResult(AdminRequestUtil.getRequestUser());
String tokenValue = StpUtil.getTokenValue();
loginResult.setToken(tokenValue);
return ResponseDTO.ok(loginResult);
}
@Operation(summary = "退出登陆 @author 卓大")

View File

@@ -124,7 +124,7 @@
<if test="query.keywords != null and query.keywords !=''">
AND ( INSTR(t_notice.title,#{query.keywords})
OR INSTR(t_notice.author,#{query.keywords})
OR INSTR(t_notice.documentNumber,#{query.keywords})
OR INSTR(t_notice.document_number,#{query.keywords})
OR INSTR(t_notice.source,#{query.keywords})
)
</if>

View File

@@ -248,6 +248,11 @@
<artifactId>poi-scratchpad</artifactId>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>ooxml-schemas</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>

View File

@@ -25,12 +25,12 @@ public class PageParam {
@Schema(description = "页码(不能为空)", example = "1")
@NotNull(message = "分页参数不能为空")
private Integer pageNum;
private Long pageNum;
@Schema(description = "每页数量(不能为空)", example = "10")
@NotNull(message = "每页数量不能为空")
@Max(value = 200, message = "每页最大为200")
private Integer pageSize;
@Max(value = 500, message = "每页最大为500")
private Long pageSize;
@Schema(description = "是否查询总条数")
protected Boolean searchCount;

View File

@@ -24,7 +24,7 @@ public class ResponseDTO<T> {
public static final int OK_CODE = 0;
public static final String OK_MSG = "success";
public static final String OK_MSG = "操作成功";
@Schema(description = "返回码")
private Integer code;

View File

@@ -0,0 +1,226 @@
package net.lab1024.sa.base.common.util;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.openxml4j.opc.PackagePartName;
import org.apache.poi.openxml4j.opc.PackageRelationship;
import org.apache.poi.openxml4j.opc.TargetMode;
import org.apache.poi.xssf.usermodel.XSSFPictureData;
import org.apache.poi.xssf.usermodel.XSSFRelation;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Collection;
/**
*
* excel 工具类
*
* @Author 1024创新实验室-主任:卓大
* @Date 2024/4/22 22:49:07
* @Wechat zhuoda1024
* @Email lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net 2012-2024
*/
public final class SmartExcelUtil {
/**
* 通用单sheet导出
*/
public static void exportExcel(HttpServletResponse response, String fileName, String sheetName, Class head,Collection<?> data) throws IOException {
// 设置下载消息头
SmartResponseUtil.setDownloadFileHeader(response, fileName, null);
// 下载
EasyExcel.write(response.getOutputStream(), head)
.autoCloseStream(Boolean.FALSE)
.sheet(sheetName)
.doWrite(data);
}
/**
* 通用单 sheet水印 导出
*/
public static void exportExcelWithWatermark(HttpServletResponse response, String fileName, String sheetName, Class head,Collection<?> data, String watermarkString) throws IOException {
// 设置下载消息头
SmartResponseUtil.setDownloadFileHeader(response, fileName, null);
// 水印
Watermark watermark = new Watermark(watermarkString);
// 一定要inMemory
EasyExcel.write(response.getOutputStream(), head)
.inMemory(true)
.sheet(sheetName)
.registerWriteHandler(new CustomWaterMarkHandler(watermark))
.doWrite(data);
}
@Slf4j
private static class CustomWaterMarkHandler implements SheetWriteHandler {
private final Watermark watermark;
public CustomWaterMarkHandler(Watermark watermark) {
this.watermark = watermark;
}
@Override
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
BufferedImage bufferedImage = createWatermarkImage();
XSSFWorkbook workbook = (XSSFWorkbook) writeSheetHolder.getParentWriteWorkbookHolder().getWorkbook();
try {
// 添加水印的具体操作
addWatermarkToSheet(workbook, bufferedImage);
} catch (Exception e) {
log.error("添加水印出错:", e);
}
}
/**
* 创建水印图片
*
* @return
*/
private BufferedImage createWatermarkImage() {
// 获取水印相关参数
Font font = watermark.getFont();
int width = watermark.getWidth();
int height = watermark.getHeight();
Color color = watermark.getColor();
String text = watermark.getContent();
// 创建带有透明背景的 BufferedImage
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = image.createGraphics();
// 设置画笔字体、平滑、颜色
g.setFont(font);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(color);
// 计算水印位置和角度
int y = watermark.getYAxis();
int x = watermark.getXAxis();
AffineTransform transform = AffineTransform.getRotateInstance(Math.toRadians(-watermark.getAngle()), 0, y);
g.setTransform(transform);
// 绘制水印文字
g.drawString(text, x, y);
// 释放资源
g.dispose();
return image;
}
private void addWatermarkToSheet(XSSFWorkbook workbook, BufferedImage watermarkImage) {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
ImageIO.write(watermarkImage, "png", os);
int pictureIdx = workbook.addPicture(os.toByteArray(), XSSFWorkbook.PICTURE_TYPE_PNG);
XSSFPictureData pictureData = workbook.getAllPictures().get(pictureIdx);
for (int i = 0; i < workbook.getNumberOfSheets(); i++) {
// 获取每个Sheet表
XSSFSheet sheet = workbook.getSheetAt(i);
PackagePartName ppn = pictureData.getPackagePart().getPartName();
String relType = XSSFRelation.IMAGES.getRelation();
PackageRelationship pr = sheet.getPackagePart().addRelationship(ppn, TargetMode.INTERNAL, relType, null);
sheet.getCTWorksheet().addNewPicture().setId(pr.getId());
}
} catch (Exception e) {
// 处理ImageIO.write可能抛出的异常
log.error("添加水印图片时发生错误", e);
}
}
}
@Data
private static class Watermark {
public Watermark(String content) {
this.content = content;
init();
}
public Watermark(String content, Color color, Font font, double angle) {
this.content = content;
this.color = color;
this.font = font;
this.angle = angle;
init();
}
/**
* 根据水印内容长度自适应水印图片大小,简单的三角函数
*/
private void init() {
FontMetrics fontMetrics = new JLabel().getFontMetrics(this.font);
int stringWidth = fontMetrics.stringWidth(this.content);
int charWidth = fontMetrics.charWidth('A');
this.width = (int) Math.abs(stringWidth * Math.cos(Math.toRadians(this.angle))) + 5 * charWidth;
this.height = (int) Math.abs(stringWidth * Math.sin(Math.toRadians(this.angle))) + 5 * charWidth;
this.yAxis = this.height;
this.xAxis = charWidth;
}
/**
* 水印内容
*/
private String content;
/**
* 画笔颜色
*/
private Color color = new Color(239,239,239);
/**
* 字体样式
*/
private Font font = new Font("Microsoft YaHei", Font.BOLD, 26);
/**
* 水印宽度
*/
private int width;
/**
* 水印高度
*/
private int height;
/**
* 倾斜角度,非弧度制
*/
private double angle = 25;
/**
* 字体的y轴位置
*/
private int yAxis = 50;
/**
* 字体的X轴位置
*/
private int xAxis;
/**
* 水平倾斜度
*/
private double shearX = 0.1;
/**
* 垂直倾斜度
*/
private double shearY = -0.26;
}
}

View File

@@ -9,9 +9,7 @@ import net.lab1024.sa.base.constant.SwaggerTagConst;
import net.lab1024.sa.base.module.support.changelog.domain.form.ChangeLogQueryForm;
import net.lab1024.sa.base.module.support.changelog.domain.vo.ChangeLogVO;
import net.lab1024.sa.base.module.support.changelog.service.ChangeLogService;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
@@ -36,4 +34,11 @@ public class ChangeLogController extends SupportBaseController {
public ResponseDTO<PageResult<ChangeLogVO>> queryPage(@RequestBody @Valid ChangeLogQueryForm queryForm) {
return ResponseDTO.ok(changeLogService.queryPage(queryForm));
}
@Operation(summary = "变更内容详情 @author 卓大")
@GetMapping("/changeLog/getDetail/{changeLogId}")
public ResponseDTO<ChangeLogVO> getDetail(@PathVariable Long changeLogId) {
return ResponseDTO.ok(changeLogService.getById(changeLogId));
}
}

View File

@@ -33,7 +33,6 @@ public class ChangeLogService {
/**
* 分页查询
*
*/
public PageResult<ChangeLogVO> queryPage(ChangeLogQueryForm queryForm) {
Page<?> page = SmartPageUtil.convert2PageQuery(queryForm);
@@ -57,7 +56,6 @@ public class ChangeLogService {
/**
* 更新
*
*/
public synchronized ResponseDTO<String> update(ChangeLogUpdateForm updateForm) {
ChangeLogEntity existVersion = changeLogDao.selectByVersion(updateForm.getVersion());
@@ -71,7 +69,6 @@ public class ChangeLogService {
/**
* 批量删除
*
*/
public synchronized ResponseDTO<String> batchDelete(List<Long> idList) {
if (CollectionUtils.isEmpty(idList)) {
@@ -93,4 +90,8 @@ public class ChangeLogService {
changeLogDao.deleteById(changeLogId);
return ResponseDTO.ok();
}
public ChangeLogVO getById(Long changeLogId) {
return SmartBeanUtil.copy(changeLogDao.selectById(changeLogId), ChangeLogVO.class);
}
}

View File

@@ -87,7 +87,7 @@ public class CodeGeneratorController extends SupportBaseController {
ResponseDTO<byte[]> download = codeGeneratorService.download(tableName);
if (download.getOk()) {
SmartResponseUtil.setDownloadFileHeader(response, tableName + "-code.zip", (long) download.getData().length);
SmartResponseUtil.setDownloadFileHeader(response, tableName + "_code.zip", (long) download.getData().length);
response.getOutputStream().write(download.getData());
} else {
SmartResponseUtil.write(response, download);

View File

@@ -55,22 +55,15 @@ public class RepeatSubmitAspect {
if (StringUtils.isEmpty(ticket)) {
return point.proceed();
}
Long timeStamp = this.repeatSubmitTicket.getTicketTimestamp(ticket);
if (timeStamp != null) {
Long lastRequestTime = this.repeatSubmitTicket.getTicketTimestamp(ticket);
if (lastRequestTime != null) {
Method method = ((MethodSignature) point.getSignature()).getMethod();
RepeatSubmit annotation = method.getAnnotation(RepeatSubmit.class);
// 说明注解去掉了
if (annotation != null) {
return point.proceed();
}
int interval = Math.min(annotation.value(), RepeatSubmit.MAX_INTERVAL);
if (System.currentTimeMillis() < timeStamp + interval) {
if (System.currentTimeMillis() < lastRequestTime + interval) {
// 提交频繁
return ResponseDTO.error(UserErrorCode.REPEAT_SUBMIT);
}
}
Object obj = null;
try {
@@ -80,8 +73,6 @@ public class RepeatSubmitAspect {
} catch (Throwable throwable) {
log.error("", throwable);
throw throwable;
} finally {
this.repeatSubmitTicket.removeTicket(ticket);
}
return obj;
}

View File

@@ -60,9 +60,9 @@ public class ${name.upperCamel}Controller {
#if($deleteInfo.deleteEnum == "Single" || $deleteInfo.deleteEnum == "SingleAndBatch")
@Operation(summary = "单个删除 @author ${basic.backendAuthor}")
@GetMapping("/${name.lowerCamel}/delete/{${name.lowerCamel}Id}")
@GetMapping("/${name.lowerCamel}/delete/{${primaryKeyFieldName}}")
public ResponseDTO<String> batchDelete(@PathVariable ${primaryKeyJavaType} ${primaryKeyFieldName}) {
return ${name.lowerCamel}Service.delete(${name.lowerCamel}Id);
return ${name.lowerCamel}Service.delete(${primaryKeyFieldName});
}
#end
#end

View File

@@ -9,6 +9,7 @@ import net.lab1024.sa.base.common.domain.ResponseDTO;
import net.lab1024.sa.base.common.domain.PageResult;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import org.apache.commons.collections4.CollectionUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

View File

@@ -3,7 +3,7 @@ spring:
datasource:
url: jdbc:p6spy:mysql://127.0.0.1:3306/smart_admin_v3?autoReconnect=true&useServerPreparedStmts=false&rewriteBatchedStatements=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true&serverTimezone=Asia/Shanghai
username: root
password: Zhuoda.123
password: Zhuoda1024lab
initial-size: 2
min-idle: 2
max-active: 10
@@ -62,25 +62,9 @@ server:
basedir: ${project.log-directory}/tomcat-logs
accesslog:
enabled: true
max-days: 7
pattern: "%t %{X-Forwarded-For}i %a %r %s (%D ms) %I (%B byte)"
# 文件上传 配置
#file:
# storage:
# mode: local
# local:
# upload-path: /home/smart_admin_v3/upload/ #文件上传目录
# url-prefix:
# cloud:
# region: oss-cn-qingdao
# endpoint: oss-cn-qingdao.aliyuncs.com
# bucket-name: common
# access-key:
# secret-key:
# url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
# private-url-expire-seconds: 3600
# 文件上传 配置
file:
storage:
@@ -108,7 +92,7 @@ springdoc:
knife4j:
enable: true
basic:
enable: true
enable: false
username: api # Basic认证用户名
password: 1024 # Basic认证密码
@@ -136,8 +120,6 @@ reload:
sa-token:
# token 名称(同时也是 cookie 名称)
token-name: x-access-token
# token 前缀 例如Bear
token-prefix:
# token 有效期(单位:秒) 默认30天2592000秒-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结

View File

@@ -62,6 +62,7 @@ server:
basedir: ${project.log-directory}/tomcat-logs
accesslog:
enabled: true
max-days: 7
pattern: "%t %{X-Forwarded-For}i %a %r %s (%D ms) %I (%B byte)"
@@ -121,8 +122,6 @@ reload:
sa-token:
# token 名称(同时也是 cookie 名称)
token-name: x-access-token
# token 前缀 例如Bear
token-prefix:
# token 有效期(单位:秒) 默认30天2592000秒-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结

View File

@@ -62,6 +62,7 @@ server:
basedir: ${project.log-directory}/tomcat-logs
accesslog:
enabled: true
max-days: 30
pattern: "%t %{X-Forwarded-For}i %a %r %s (%D ms) %I (%B byte)"
@@ -121,8 +122,6 @@ reload:
sa-token:
# token 名称(同时也是 cookie 名称)
token-name: x-access-token
# token 前缀 例如Bear
token-prefix:
# token 有效期(单位:秒) 默认30天2592000秒-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结

View File

@@ -62,6 +62,7 @@ server:
basedir: ${project.log-directory}/tomcat-logs
accesslog:
enabled: true
max-days: 7
pattern: "%t %{X-Forwarded-For}i %a %r %s (%D ms) %I (%B byte)"
@@ -121,8 +122,6 @@ reload:
sa-token:
# token 名称(同时也是 cookie 名称)
token-name: x-access-token
# token 前缀 例如Bear
token-prefix:
# token 有效期(单位:秒) 默认30天2592000秒-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结

File diff suppressed because it is too large Load Diff

View File

@@ -19,33 +19,32 @@
"dependencies": {
"@wangeditor/editor": "5.1.14",
"@wangeditor/editor-for-vue": "5.1.12",
"ant-design-vue": "4.0.7",
"axios": "1.4.0",
"ant-design-vue": "4.2.0",
"axios": "1.6.8",
"clipboard": "2.0.11",
"crypto-js": "4.1.1",
"decimal.js": "10.3.1",
"diff": "5.1.0",
"diff2html": "3.4.18",
"diff": "5.2.0",
"diff2html": "3.4.47",
"echarts": "5.4.3",
"highlight.js": "11.8.0",
"js-cookie": "3.0.5",
"lodash": "4.17.21",
"lunar-javascript": "1.6.3",
"mitt": "3.0.0",
"lunar-javascript": "1.6.12",
"mitt": "3.0.1",
"nprogress": "0.2.0",
"pinia": "2.1.6",
"sm-crypto": "^0.3.13",
"pinia": "2.1.7",
"sm-crypto": "0.3.13",
"sortablejs": "1.15.0",
"ua-parser-js": "1.0.35",
"v-viewer": "~1.6.4",
"vue": "3.3.4",
"vue-i18n": "9.2.2",
"vue-router": "4.2.4",
"vue": "3.3.13",
"vue-i18n": "9.10.2",
"vue-router": "4.3.0",
"vue3-json-viewer": "2.2.2"
},
"devDependencies": {
"@vitejs/plugin-vue": "4.5.0",
"@vue/compiler-sfc": "3.3.4",
"@vitejs/plugin-vue": "5.0.4",
"@vue/compiler-sfc": "3.4.21",
"eslint": "^8.16.0",
"eslint-config-prettier": "~9.0.0",
"eslint-plugin-prettier": "~5.0.0",
@@ -58,9 +57,9 @@
"stylelint-config-prettier": "~9.0.3",
"stylelint-config-standard": "~25.0.0",
"stylelint-order": "~5.0.0",
"terser": "~5.19.2",
"vite": "5.0.0",
"vue-eslint-parser": "~9.3.1"
"terser": "~5.29.2",
"vite": "5.2.6",
"vue-eslint-parser": "~9.4.2"
},
"engines": {
"node": ">=18"

View File

@@ -9,7 +9,29 @@
-->
<template>
<a-config-provider :locale="antdLocale">
<a-config-provider
:locale="antdLocale"
:theme="{
algorithm: compactFlag ? theme.compactAlgorithm : theme.defaultAlgorithm,
token: {
colorPrimary: themeColors[colorIndex].primaryColor,
colorLink: themeColors[colorIndex].primaryColor,
colorLinkActive: themeColors[colorIndex].activeColor,
colorLinkHover: themeColors[colorIndex].hoverColor,
colorIcon: themeColors[colorIndex].primaryColor,
},
components: {
Button: {
colorLink: themeColors[colorIndex].primaryColor,
colorLinkActive: themeColors[colorIndex].activeColor,
colorLinkHover: themeColors[colorIndex].hoverColor,
},
Icon: {
colorIcon: themeColors[colorIndex].primaryColor,
},
},
}"
>
<!---全局loading--->
<a-spin :spinning="spinning" tip="稍等片刻,我在拼命加载中..." size="large">
<!--- 路由 -->
@@ -23,12 +45,21 @@
import { computed } from 'vue';
import { messages } from '/@/i18n';
import { useAppConfigStore } from '/@/store/modules/system/app-config';
import { useSpinStore } from './store/modules/system/spin';
import { useSpinStore } from '/@/store/modules/system/spin';
import { theme } from 'ant-design-vue';
import { themeColors } from '/@/theme/color.js';
const antdLocale = computed(() => messages[useAppConfigStore().language].antdLocale);
const dayjsLocale = computed(() => messages[useAppConfigStore().language].dayjsLocale);
dayjs.locale(dayjsLocale);
// 全局loading
let spinStore = useSpinStore();
const spinning = computed(() => spinStore.loading);
// 是否紧凑
const compactFlag = computed(() => useAppConfigStore().compactFlag);
// 主题颜色
const colorIndex = computed(() => {
return useAppConfigStore().colorIndex;
});
</script>

View File

@@ -32,7 +32,7 @@ export const fileApi = {
/**
* 下载文件流根据fileKey @author 胡克
*/
downLoadFile: (fileName, fileKey) => {
downLoadFile: (fileKey) => {
return getDownload('/support/file/downLoad', { fileKey });
},
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 234 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -24,7 +24,6 @@
<script setup>
import { ref } from 'vue';
import { getDownload } from '/@/lib/axios';
import { fileApi } from '/src/api/support/file-api';
import { smartSentry } from '/@/lib/smart-sentry';
import { SmartLoading } from '/@/components/framework/smart-loading';
@@ -62,7 +61,7 @@
setVisible(true);
return;
}
window.open(fileItem.fileUrl);
fileApi.downLoadFile(fileItem.fileKey);
}
// 判断图片类型

View File

@@ -22,8 +22,9 @@
v-for="(item, index) in fileList"
:key="index"
:src="item.fileUrl"
:style="{ display: type === 'text' ? 'none' : '' }"
:style="{ display: type === 'text' ? 'none' : '', padding: '2px', height: '100px' }"
:width="width"
@click="preview(item, index)"
/>
</a-image-preview-group>
</a-space>
@@ -31,7 +32,7 @@
</template>
<script setup>
import { ref } from 'vue';
import { getDownload } from '/@/lib/axios';
import { fileApi } from '/src/api/support/file-api';
let props = defineProps({
fileList: {
@@ -45,10 +46,10 @@
type: String,
default: 'text',
},
// image宽度
// image 宽度
width: {
type: Number,
default: 150,
default: 100,
},
// 分隔符 可设置html标签 例如:<br/>
separator: {
@@ -64,7 +65,7 @@
previewCurrent.value = index;
visible.value = true;
} else {
window.open(file.fileUrl);
fileApi.downLoadFile(file.fileKey);
}
}

View File

@@ -11,6 +11,7 @@
<template>
<div class="clearfix">
<a-upload
multiple
:accept="props.accept"
:before-upload="beforeUpload"
:customRequest="customRequest"
@@ -43,12 +44,11 @@
</template>
<script setup>
import { computed, ref, watch } from 'vue';
import { message } from 'ant-design-vue';
import { Modal } from 'ant-design-vue';
import { fileApi } from '/src/api/support/file-api';
import { useUserStore } from '/@/store/modules/system/user';
import { SmartLoading } from '/@/components/framework/smart-loading';
import { FILE_FOLDER_TYPE_ENUM } from '/@/constants/support/file-const';
import { getDownload } from '/@/lib/axios';
import { smartSentry } from '/@/lib/smart-sentry';
const props = defineProps({
value: String,
@@ -162,14 +162,44 @@
console.log(fileList.value);
}
function beforeUpload(file) {
function beforeUpload(file, files) {
if (fileList.value.length + files.length > props.maxUploadSize) {
showErrorMsgOnce(`最多支持上传 ${props.maxUploadSize} 个文件哦!`);
return false;
}
if (props.accept) {
const suffixIndex = file.name.lastIndexOf('.');
const fileSuffix = file.name.substring(suffixIndex <= -1 ? 0 : suffixIndex);
if (props.accept.indexOf(fileSuffix) === -1) {
showErrorMsgOnce(`只支持上传 ${props.accept.replaceAll(',', ' ')} 格式的文件`);
return false;
}
}
const isLimitSize = file.size / 1024 / 1024 < props.maxSize;
if (!isLimitSize) {
return message.error(`上传的文件必须小于${props.maxSize}Mb`);
showErrorMsgOnce(`单个文件大小必须小于 ${props.maxSize} Mb`);
}
return isLimitSize;
}
const showErrorModalFlag = ref(true);
const showErrorMsgOnce = (content) => {
if (showErrorModalFlag.value) {
Modal.error({
title: '提示',
content: content,
okType: 'danger',
centered: true,
onOk() {
showErrorModalFlag.value = true;
},
});
showErrorModalFlag.value = false;
}
};
function handleCancel() {
previewVisible.value = false;
}
@@ -179,7 +209,7 @@
previewUrl.value = file.url || file.preview;
previewVisible.value = true;
} else {
getDownload(file.fileName, file.fileUrl);
fileApi.downLoadFile(file.fileKey);
}
};

View File

@@ -16,6 +16,8 @@ export const appDefaultConfig = {
sideMenuWidth: 200,
// 菜单主题
sideMenuTheme: 'dark',
// 主题颜色索引
colorIndex: 0,
// 顶部菜单页面宽度
pageWidth: '99%',
// 标签页
@@ -31,5 +33,7 @@ export const appDefaultConfig = {
// 网站名称
websiteName: 'SmartAdmin 3.X',
// 主题颜色
primaryColor: 'red',
primaryColor: '#1677ff',
// 紧凑
compactFlag: false,
};

View File

@@ -16,6 +16,8 @@ const KEY_PREFIX = 'smart_admin_';
* localStorageKey集合
*/
export default {
// 用户token
USER_TOKEN: `${KEY_PREFIX}user_token`,
// 用户信息
USER_INFO: `${KEY_PREFIX}user_info`,
// 用户权限点

View File

@@ -14,10 +14,12 @@ export default {
antdLocale: antd,
dayjsLocale: dayjs,
'setting.title': 'Setting',
'setting.color': 'Theme Color',
'setting.menu.layout': 'Menu Layout',
'setting.menu.width': 'Menu Width',
'setting.menu.theme': 'Menu Theme',
'setting.page.width': 'Page Width',
'setting.compact': 'Page Compact',
'setting.bread': 'Show Bread',
'setting.pagetag': 'Show PageTag',
'setting.footer': 'Show Footer',

View File

@@ -14,9 +14,11 @@ export default {
antdLocale: antd,
dayjsLocale: dayjs,
'setting.title': '网站设置',
'setting.color': '主题颜色',
'setting.menu.layout': '菜单布局',
'setting.menu.width': '菜单宽度',
'setting.menu.theme': '菜单主题',
'setting.compact': '页面紧凑',
'setting.page.width': '页面宽度',
'setting.bread': '面包屑',
'setting.pagetag': '标签页',

View File

@@ -33,7 +33,6 @@
import { computed, ref, onMounted } from 'vue';
import { loginApi } from '/src/api/system/login-api';
import { useUserStore } from '/@/store/modules/system/user';
import { clearAllCoolies } from '/@/utils/cookie-util';
import { localClear } from '/@/utils/local-util';
import { smartSentry } from '/@/lib/smart-sentry';
import HeaderResetPassword from './header-reset-password-modal/index.vue';
@@ -48,9 +47,7 @@
} catch (e) {
smartSentry.captureError(e);
} finally {
localClear();
clearAllCoolies();
useUserStore().logout();
location.reload();
}

View File

@@ -11,10 +11,35 @@
<template>
<a-drawer :title="$t('setting.title')" placement="right" :open="visible" @close="close">
<a-form layout="horizontal" :label-col="{ span: 8 }">
<a-form-item label="语言/Language">
<a-select v-model:value="formState.language" @change="changeLanguage" style="width: 120px">
<a-select-option v-for="item in i18nList" :key="item.value" :value="item.value">{{ item.text }}</a-select-option>
</a-select>
<a-form-item :label="$t('setting.color')">
<div style="display: flex; align-items: center">
<template v-for="(item, index) in themeColors">
<div v-if="index === formState.colorIndex" class="color">
<CheckSquareFilled :style="{ color: item.primaryColor, fontSize: '22px' }" />
</div>
<div v-else @click="changeColor(index)" class="color">
<svg
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
:fill="item.primaryColor"
xmlns="http://www.w3.org/2000/svg"
width="26"
height="26"
>
<path
d="M128 160.01219c0-17.67619 14.336-32.01219 32.01219-32.01219h704c17.65181 0 31.98781 14.336 31.98781 32.01219v704c0 17.65181-14.336 31.98781-32.01219 31.98781H160.036571a31.98781 31.98781 0 0 1-32.01219-32.01219V160.036571z"
></path>
</svg>
</div>
</template>
</div>
</a-form-item>
<a-form-item :label="$t('setting.compact')">
<a-radio-group v-model:value="formState.compactFlag" button-style="solid" @change="changeCompactFlag">
<a-radio-button :value="false">默认</a-radio-button>
<a-radio-button :value="true">紧凑</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item :label="$t('setting.menu.layout')">
<a-radio-group @change="changeLayout" button-style="solid" v-model:value="formState.layout">
@@ -23,6 +48,12 @@
</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item :label="$t('setting.menu.theme')">
<a-radio-group v-model:value="formState.sideMenuTheme" button-style="solid" @change="changeMenuTheme">
<a-radio-button value="dark">Dark</a-radio-button>
<a-radio-button value="light">Light</a-radio-button>
</a-radio-group>
</a-form-item>
<a-form-item :label="$t('setting.menu.width')" v-if="formState.layout === LAYOUT_ENUM.SIDE.value">
<a-input-number @change="changeSideMenuWidth" v-model:value="formState.sideMenuWidth" :min="1" />
像素px
@@ -31,11 +62,10 @@
<a-input @change="changePageWidth" v-model:value="formState.pageWidth" />
像素px或者 百分比
</a-form-item>
<a-form-item :label="$t('setting.menu.theme')">
<a-radio-group v-model:value="formState.sideMenuTheme" button-style="solid" @change="changeMenuTheme">
<a-radio-button value="dark">Dark</a-radio-button>
<a-radio-button value="light">Light</a-radio-button>
</a-radio-group>
<a-form-item label="语言/Language">
<a-select v-model:value="formState.language" @change="changeLanguage" style="width: 120px">
<a-select-option v-for="item in i18nList" :key="item.value" :value="item.value">{{ item.text }}</a-select-option>
</a-select>
</a-form-item>
<a-form-item :label="$t('setting.bread')">
<a-switch @change="changeBreadCrumbFlag" v-model:checked="formState.breadCrumbFlag" checked-children="显示" un-checked-children="隐藏" />
@@ -69,6 +99,7 @@
import { useAppConfigStore } from '/@/store/modules/system/app-config';
import { Modal } from 'ant-design-vue';
import { appDefaultConfig } from '/@/config/app-config';
import { themeColors } from '/@/theme/color.js';
// ----------------- modal 显示与隐藏 -----------------
@@ -130,10 +161,14 @@
layout: appConfigStore.layout,
// 页面宽度
pageWidth: appConfigStore.pageWidth,
// 颜色
colorIndex: appConfigStore.colorIndex,
// 侧边菜单宽度
sideMenuWidth: appConfigStore.sideMenuWidth,
// 菜单主题
sideMenuTheme: appConfigStore.sideMenuTheme,
// 页面紧凑
compactFlag: appConfigStore.compactFlag,
// 标签页
pageTagFlag: appConfigStore.pageTagFlag,
// 面包屑
@@ -162,6 +197,13 @@
});
}
function changeColor(index) {
formState.colorIndex = index;
appConfigStore.$patch({
colorIndex: index,
});
}
function changeSideMenuWidth(value) {
appConfigStore.$patch({
sideMenuWidth: value,
@@ -180,6 +222,12 @@
});
}
function changeCompactFlag(e) {
appConfigStore.$patch({
compactFlag: e.target.value,
});
}
function changeBreadCrumbFlag(e) {
appConfigStore.$patch({
breadCrumbFlag: e,
@@ -222,4 +270,14 @@
text-align: left;
z-index: 1;
}
.color {
margin-left: 8px;
display: inline;
height: 26px;
width: 26px;
display: flex;
justify-content: center;
align-items: center;
}
</style>

View File

@@ -34,9 +34,9 @@
<HeaderAvatar />
</div>
<!---帮助文档--->
<div class="user-space-item" @click="showHelpDoc">
<question-circle-two-tone style="font-size: 18px; margin-right: 5px; margin-top: 5px" />
<div class="user-space-item" @click="showHelpDoc" v-if="!showHelpDocFlag">
<span>帮助文档</span>
<DoubleLeftOutlined v-if="!showHelpDocFlag" />
</div>
<HeaderSetting ref="headerSetting" />
@@ -48,7 +48,8 @@
import HeaderSetting from './header-setting.vue';
import HeaderMessage from './header-message.vue';
import { useAppConfigStore } from '/@/store/modules/system/app-config';
import { ref } from 'vue';
import { computed, ref } from 'vue';
import { theme } from 'ant-design-vue';
// 设置
const headerSetting = ref();
@@ -67,10 +68,17 @@
useAppConfigStore().showHelpDoc();
}
const showHelpDocFlag = computed(() => {
return useAppConfigStore().helpDocFlag;
});
//搜索
function search(){
window.open("https://1024lab.net");
function search() {
window.open('https://1024lab.net');
}
const { useToken } = theme;
const { token } = useToken();
</script>
<style lang="less" scoped>
@@ -91,7 +99,7 @@
}
.user-space-item:hover {
color: @primary-color;
color: v-bind('token.colorPrimary');
background: @hover-bg-color;
}

View File

@@ -12,7 +12,7 @@
<a-row style="border-bottom: 1px solid #eeeeee; position: relative" v-show="pageTagFlag">
<a-dropdown :trigger="['contextmenu']">
<div class="smart-page-tag">
<a-tabs style="width: 100%" :tab-position="mode" v-model:activeKey="selectedKey" size="small" @tabClick="selectTab" >
<a-tabs style="width: 100%" :tab-position="mode" v-model:activeKey="selectedKey" size="small" @tabClick="selectTab">
<a-tab-pane v-for="item in tagNav" :key="item.menuName">
<template #tab>
<span>
@@ -55,12 +55,7 @@
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
import { useAppConfigStore } from '/@/store/modules/system/app-config';
import { useUserStore } from '/@/store/modules/system/user';
// //样式
// const tagOperateWidth = ref(40);
// const tabBarStyle = {
// width: 'calc(100% - 80px)'
// }
import { theme } from 'ant-design-vue';
//标签页 是否显示
const pageTagFlag = computed(() => useAppConfigStore().$state.pageTagFlag);
@@ -85,7 +80,7 @@
return;
}
// 寻找tag
let tag = tagNav.value.find((e) => e.menuName == name);
let tag = tagNav.value.find((e) => e.menuName === name);
if (!tag) {
router.push({ name: HOME_PAGE_NAME });
return;
@@ -96,7 +91,7 @@
//通过菜单关闭
function closeByMenu(closeAll) {
let find = tagNav.value.find((e) => e.menuName == selectedKey.value);
let find = tagNav.value.find((e) => e.menuName === selectedKey.value);
if (!find || closeAll) {
closeTag(null, true);
} else {
@@ -110,12 +105,12 @@
if (item && !closeAll) {
let goName = HOME_PAGE_NAME;
let goQuery = undefined;
if (item.fromMenuName && tagNav.value.some((e) => e.menuName == item.fromMenuName)) {
if (item.fromMenuName && tagNav.value.some((e) => e.menuName === item.fromMenuName)) {
goName = item.fromMenuName;
goQuery = item.fromMenuQuery;
} else {
// 查询左侧tag
let index = tagNav.value.findIndex((e) => e.menuName == item.menuName);
let index = tagNav.value.findIndex((e) => e.menuName === item.menuName);
if (index > 0) {
// 查询左侧tag
let leftTagNav = tagNav.value[index - 1];
@@ -132,10 +127,14 @@
// 关闭其他tag不做处理 直接调用closeTagNav
useUserStore().closeTagNav(item ? item.menuName : null, closeAll);
}
const { useToken } = theme;
const { token } = useToken();
</script>
<style scoped lang="less">
@smart-page-tag-operate-width: 40px;
@color-primary: v-bind('token.colorPrimary');
.smart-page-tag-operate {
width: @smart-page-tag-operate-width;
@@ -164,7 +163,7 @@
}
.smart-page-tag-operate:hover {
color: @primary-color;
color: @color-primary;
}
.smart-page-tag {
@@ -184,7 +183,7 @@
.smart-page-tag-close {
margin-left: 5px;
font-size: 10px;
color: #8c8c8c;
color: #666666;
}
/** 覆盖 ant design vue的 tabs 样式,变小一点 **/
@@ -203,15 +202,15 @@
}
:deep(.ant-tabs-tab-active) {
background-color: #e8f4ff;
background-color: #eeeeee;
.smart-page-tag-close {
color: @primary-color;
color: @color-primary;
}
}
:deep(.ant-tabs-nav .ant-tabs-tab:hover) {
background-color: #e8f4ff;
background-color: #eeeeee;
.smart-page-tag-close {
color: @primary-color;
color: @color-primary;
}
}
}

View File

@@ -90,17 +90,20 @@
z-index: 100;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
.logo-img {
width: 40px;
height: 40px;
width: 30px;
height: 30px;
}
.title {
font-size: 16px;
font-weight: 600;
overflow: hidden;
word-wrap: break-word;
white-space: nowrap;
color: v-bind('theme === "light" ? "#001529": "#ffffff"');
}
}

View File

@@ -1,11 +1,11 @@
<!--
* 传统菜单
*
* @Author: 1024创新实验室-主任卓大
* @Date: 2022-09-06 20:29:12
* @Wechat: zhuda1024
* @Email: lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net Since 2012
*
* @Author: 1024创新实验室-主任卓大
* @Date: 2022-09-06 20:29:12
* @Wechat: zhuda1024
* @Email: lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net Since 2012
-->
<template>
<!--左侧菜单分为两部分1顶部logo区域包含 logo和名称;2下方菜单区域-->
@@ -21,7 +21,9 @@
</div>
<!-- 2下方菜单区域 这里使用一个递归菜单解决 -->
<RecursionMenu :collapsed="collapsed" ref="menuRef" />
<div class="menu">
<RecursionMenu :collapsed="collapsed" ref="menuRef" />
</div>
</template>
<script setup>
@@ -60,6 +62,13 @@
function onGoHome() {
router.push({ name: HOME_PAGE_NAME });
}
const color = computed(() => {
let isLight = useAppConfigStore().$state.sideMenuTheme === 'light';
return {
background: isLight ? '#FFFFFF' : '#001529',
};
});
</script>
<style lang="less" scoped>
@@ -75,28 +84,34 @@
height: @header-user-height;
line-height: @header-user-height;
padding: 0px 15px 0px 15px;
width: 100%;
background-color: v-bind('color.background');
position: fixed;
width: 80px;
z-index: 21;
display: flex;
justify-content: center;
align-items: center;
.logo-img {
width: 32px;
height: 32px;
width: 30px;
height: 30px;
}
}
.logo {
height: @header-user-height;
line-height: @header-user-height;
background-color: v-bind('color.background');
padding: 0px 15px 0px 15px;
position: fixed;
z-index: 21;
display: flex;
cursor: pointer;
justify-content: center;
align-items: center;
.logo-img {
width: 40px;
height: 40px;
width: 30px;
height: 30px;
}
.title {
@@ -113,6 +128,6 @@
}
}
.menu {
padding: 16px 0;
margin-top: @header-user-height;
}
</style>

View File

@@ -86,10 +86,10 @@
const color = computed(() => {
let isLight = useAppConfigStore().$state.sideMenuTheme === 'light';
return {
color: isLight ? '#001529' : '#FFFFFF',
background:isLight ? '#FFFFFF' : '#001529',
}
return {
color: isLight ? '#001529' : '#FFFFFF',
background: isLight ? '#FFFFFF' : '#001529',
};
});
const router = useRouter();
@@ -118,7 +118,7 @@
.logo-img {
display: inline-block;
height: 45px;
height: 30px;
vertical-align: middle;
}
.title {
@@ -161,13 +161,13 @@
color: v-bind('color.color');
}
.user-space-item{
.user-space-item {
margin-left: 10px;
}
}
}
:deep(.ant-menu-horizontal){
border-bottom:0;
:deep(.ant-menu-horizontal) {
border-bottom: 0;
}
</style>

View File

@@ -9,11 +9,11 @@
*/
import { message, Modal } from 'ant-design-vue';
import axios from 'axios';
import { clearAllCoolies, getTokenFromCookie } from '/@/utils/cookie-util';
import { localClear } from '/@/utils/local-util';
import { localClear, localRead } from '/@/utils/local-util';
import { decryptData, encryptData } from './encrypt';
import { DATA_TYPE_ENUM } from '../constants/common-const';
import _ from 'lodash';
import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
// token的消息头
const TOKEN_HEADER = 'x-access-token';
@@ -23,12 +23,18 @@ const smartAxios = axios.create({
baseURL: import.meta.env.VITE_APP_API_URL,
});
// 退出系统
function logout() {
localClear();
location.href = '/';
}
// ================================= 请求拦截器 =================================
smartAxios.interceptors.request.use(
(config) => {
// 在发送请求之前消息头加入token token
const token = getTokenFromCookie();
const token = localRead(LocalStorageKeyConst.USER_TOKEN);
if (token) {
config.headers[TOKEN_HEADER] = token;
} else {
@@ -73,11 +79,7 @@ smartAxios.interceptors.response.use(
if (res.code === 30007 || res.code === 30008) {
message.destroy();
message.error('您没有登录,请重新登录');
clearAllCoolies();
localClear();
setTimeout(() => {
location.href = '/';
}, 300);
setTimeout(logout, 300);
return Promise.reject(response);
}
@@ -95,16 +97,9 @@ smartAxios.interceptors.response.use(
Modal.error({
title: '重要提醒',
content: res.msg,
onOk() {
return new Promise((resolve, reject) => {
clearAllCoolies();
localClear();
setTimeout(() => {
location.href = '/';
}, 300);
}).catch(() => console.log('Oops errors!'));
},
onOk: logout,
});
setTimeout(logout, 3000);
return Promise.reject(response);
}
message.destroy();

View File

@@ -25,8 +25,10 @@ import smartEnumPlugin from '/@/plugins/smart-enums-plugin';
import { buildRoutes, router } from '/@/router';
import { store } from '/@/store';
import { useUserStore } from '/@/store/modules/system/user';
import 'ant-design-vue/dist/reset.css';
import '/@/theme/index.less';
import { getTokenFromCookie } from '/@/utils/cookie-util';
import { localRead } from '/@/utils/local-util.js';
import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
/*
* -------------------- ※ 着重 解释说明下main.js的初始化逻辑 begin ※ --------------------
@@ -82,7 +84,7 @@ function initVue() {
}
//不需要获取用户信息、用户菜单、用户菜单动态路由直接初始化vue即可
let token = getTokenFromCookie();
let token = localRead(LocalStorageKeyConst.USER_TOKEN);
if (!token) {
initVue();
} else {

View File

@@ -16,9 +16,9 @@ import { PAGE_PATH_404, PAGE_PATH_LOGIN } from '/@/constants/common-const';
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
import SmartLayout from '../layout/index.vue';
import { useUserStore } from '/@/store/modules/system/user';
import { clearAllCoolies, getTokenFromCookie } from '/@/utils/cookie-util';
import { localClear } from '/@/utils/local-util';
import { localClear, localRead } from '/@/utils/local-util';
import _ from 'lodash';
import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
export const router = createRouter({
history: createWebHashHistory(),
@@ -39,9 +39,8 @@ router.beforeEach(async (to, from, next) => {
}
// 验证登录
const token = getTokenFromCookie();
const token = localRead(LocalStorageKeyConst.USER_TOKEN);
if (!token) {
clearAllCoolies();
localClear();
next({ path: PAGE_PATH_LOGIN });
return;

View File

@@ -17,5 +17,5 @@ export const loginRouters = [
title: '登录',
hideInMenu: true,
},
}
},
];

View File

@@ -12,8 +12,8 @@ import { defineStore } from 'pinia';
import localKey from '/@/constants/local-storage-key-const';
import { HOME_PAGE_NAME } from '/@/constants/system/home-const';
import { MENU_TYPE_ENUM } from '/@/constants/system/menu-const';
import { getTokenFromCookie } from '/@/utils/cookie-util';
import { localClear, localRead, localSave } from '/@/utils/local-util';
import LocalStorageKeyConst from '/@/constants/local-storage-key-const';
export const useUserStore = defineStore({
id: 'userStore',
@@ -61,7 +61,7 @@ export const useUserStore = defineStore({
if (state.token) {
return state.token;
}
return getTokenFromCookie();
return localRead(LocalStorageKeyConst.USER_TOKEN);
},
//是否初始化了 路由
getMenuRouterInitFlag(state) {

View File

@@ -0,0 +1,39 @@
export const themeColors = [
// 蓝色
{
primaryColor: '#1677ff',
activeColor: '#0958d9',
hoverColor: '#bae0ff',
},
// 绿色
{
primaryColor: '#00b96b',
activeColor: '#00945b',
hoverColor: '#20c77c',
},
// 红色
{
primaryColor: '#F5222D',
activeColor: '#cf1322',
hoverColor: '#ff4d4f',
},
// 青色
{
primaryColor: '#13c2c2',
activeColor: '#08979c',
hoverColor: '#36cfc9',
},
// 粉色
{
primaryColor: '#EB2F96',
activeColor: '#c41d7f',
hoverColor: '#f759ab',
},
// 紫色
{
primaryColor: '#722ED1',
activeColor: '#531dab',
hoverColor: '#9254de',
},
];

View File

@@ -1,29 +0,0 @@
/*
* cookie相关操作
*
* @Author: 1024创新实验室-主任:卓大
* @Date: 2022-09-06 20:58:49
* @Wechat: zhuda1024
* @Email: lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net Since 2012
*/
import Cookies from 'js-cookie';
export const COOKIE_TOKEN_KEY = 'user_token';
export const clearAllCoolies = () => {
Cookies.remove(COOKIE_TOKEN_KEY);
};
export const getTokenFromCookie = () => {
return Cookies.get(COOKIE_TOKEN_KEY);
};
/**
* 一年后cookie过期
*
* @param token
*/
export const saveTokenToCookie = (token) => {
Cookies.set(COOKIE_TOKEN_KEY, token, { expires: 365 });
};

View File

@@ -7,6 +7,7 @@
* @Email: lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net Since 2012
*/
export const localSave = (key, value) => {
localStorage.setItem(key, value);
};

View File

@@ -24,7 +24,7 @@
<a-descriptions-item label="创建时间">{{ detail.createTime }}</a-descriptions-item>
<a-descriptions-item label="创建人">{{ detail.createUserName }}</a-descriptions-item>
<a-descriptions-item label="营业执照">
<FilePreview :default-file-list="detail.businessLicense" />
<FilePreview :file-list="detail.businessLicense" />
</a-descriptions-item>
</a-descriptions>
</div>

View File

@@ -1,11 +1,11 @@
<!--
* 公司列表
*
* @Author: 1024创新实验室-主任卓大
*
* @Author: 1024创新实验室-主任卓大
* @Date: 2022-08-15 20:15:49
* @Wechat: zhuda1024
* @Email: lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net Since 2012
* @Wechat: zhuda1024
* @Email: lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net Since 2012
-->
<template>
<a-form class="smart-query-form" v-privilege="'oa:enterprise:query'">
@@ -50,7 +50,7 @@
<template #icon>
<FileExcelOutlined />
</template>
导出数据
导出数据带水印
</a-button>
</div>
<div class="smart-table-setting-block">

View File

@@ -9,7 +9,7 @@
-->
<template>
<a-card style="margin-bottom: 15px" size="small">
<a-descriptions :title="noticeDetail.title" :columns="4" size="small">
<a-descriptions :title="noticeDetail.title" :column="4" size="small">
<template #extra>
<a-button v-if="!noticeDetail.publishFlag" type="primary" size="small" @click="onEdit">编辑</a-button>
</template>
@@ -28,7 +28,7 @@
<a class="file-item" v-for="item in noticeDetail.attachment" :key="item.fileId" @click="onPrevFile(item)">{{ item.fileName }}</a>
</div>
</a-descriptions-item>
<a-descriptions-item label="可见范围" :span="2">
<a-descriptions-item label="可见范围">
<template v-if="noticeDetail.allVisibleFlag">全部可见</template>
<div class="visible-list">
<div class="visible-item" v-for="item in noticeDetail.visibleRangeList" :key="item.dataId">
@@ -59,86 +59,86 @@
</template>
<script setup>
import { onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import NoticeFormDrawer from './components/notice-form-drawer.vue';
import NoticeViewRecordList from './components/notice-view-record-list.vue';
import { noticeApi } from '/@/api/business/oa/notice-api';
import { SmartLoading } from '/@/components/framework/smart-loading';
import FilePreviewModal from '/@/components/support/file-preview-modal/index.vue';
import { smartSentry } from '/@/lib/smart-sentry';
import { onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import NoticeFormDrawer from './components/notice-form-drawer.vue';
import NoticeViewRecordList from './components/notice-view-record-list.vue';
import { noticeApi } from '/@/api/business/oa/notice-api';
import { SmartLoading } from '/@/components/framework/smart-loading';
import FilePreviewModal from '/@/components/support/file-preview-modal/index.vue';
import { smartSentry } from '/@/lib/smart-sentry';
const route = useRoute();
const route = useRoute();
const props = defineProps({
newsType: {
type: Number,
},
});
const props = defineProps({
newsType: {
type: Number,
},
});
const activeKey = ref(1);
const activeKey = ref(1);
const noticeDetail = ref({});
const noticeViewRecordList = ref();
const noticeDetail = ref({});
const noticeViewRecordList = ref();
onMounted(() => {
if (route.query.noticeId) {
queryNoticeDetail();
noticeViewRecordList.value.onSearch();
onMounted(() => {
if (route.query.noticeId) {
queryNoticeDetail();
noticeViewRecordList.value.onSearch();
}
});
// 查询详情
async function queryNoticeDetail() {
try {
SmartLoading.show();
const result = await noticeApi.getUpdateNoticeInfo(route.query.noticeId);
noticeDetail.value = result.data;
} catch (err) {
smartSentry.captureError(err);
} finally {
SmartLoading.hide();
}
}
});
// 查询详情
async function queryNoticeDetail() {
try {
SmartLoading.show();
const result = await noticeApi.getUpdateNoticeInfo(route.query.noticeId);
noticeDetail.value = result.data;
} catch (err) {
smartSentry.captureError(err);
} finally {
SmartLoading.hide();
// 点击编辑
const noticeFormDrawerRef = ref();
function onEdit() {
noticeFormDrawerRef.value.showModal(noticeDetail.value.noticeId);
}
}
// 点击编辑
const noticeFormDrawerRef = ref();
function onEdit() {
noticeFormDrawerRef.value.showModal(noticeDetail.value.noticeId);
}
// 预览附件
const filePreviewRef = ref();
function onPrevFile(fileItem) {
filePreviewRef.value.showPreview(fileItem);
}
// 预览附件
const filePreviewRef = ref();
function onPrevFile(fileItem) {
filePreviewRef.value.showPreview(fileItem);
}
</script>
<style lang="less" scoped>
:deep(.ant-descriptions-item-content) {
flex: 1;
overflow: hidden;
}
.file-list {
width: 100%;
display: flex;
flex-wrap: wrap;
.file-item {
display: block;
margin-right: 10px;
:deep(.ant-descriptions-item-content) {
flex: 1;
overflow: hidden;
}
}
.visible-list {
display: flex;
flex-wrap: wrap;
.visible-item {
margin-right: 10px;
color: #666;
.file-list {
width: 100%;
display: flex;
flex-wrap: wrap;
.file-item {
display: block;
margin-right: 10px;
}
}
}
.content-html {
img {
max-width: 100%;
.visible-list {
display: flex;
flex-wrap: wrap;
.visible-item {
margin-right: 10px;
color: #666;
}
}
.content-html {
img {
max-width: 100%;
}
}
}
</style>

View File

@@ -27,127 +27,119 @@
<!--endprint-->
</div>
<a-divider />
<div>附件<file-preview :fileList="noticeDetail.attachment" /></div>
<div>
附件
<file-preview v-if="!$lodash.isEmpty(noticeDetail.attachment)" :fileList="noticeDetail.attachment" />
<span v-else></span>
</div>
</a-card>
<a-card title="记录" size="small" class="smart-margin-top10">
<NoticeViewRecordList ref="noticeViewRecordList" :noticeId="route.query.noticeId" />
</a-card>
<!-- 预览附件 -->
<FilePreviewModal ref="filePreviewRef" />
</template>
<script setup>
import { onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import NoticeViewRecordList from './components/notice-view-record-list.vue';
import { noticeApi } from '/@/api/business/oa/notice-api';
import { SmartLoading } from '/@/components/framework/smart-loading';
import FilePreviewModal from '/@/components/support/file-preview-modal/index.vue';
import FilePreview from '/@/components/support/file-preview/index.vue';
import { smartSentry } from '/@/lib/smart-sentry';
import { onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import NoticeViewRecordList from './components/notice-view-record-list.vue';
import { noticeApi } from '/@/api/business/oa/notice-api';
import { SmartLoading } from '/@/components/framework/smart-loading';
import FilePreview from '/@/components/support/file-preview/index.vue';
import { smartSentry } from '/@/lib/smart-sentry';
const route = useRoute();
const route = useRoute();
const activeKey = ref(1);
const noticeDetail = ref({});
const noticeDetail = ref({});
onMounted(() => {
if (route.query.noticeId) {
queryNoticeDetail();
}
});
onMounted(() => {
if (route.query.noticeId) {
queryNoticeDetail();
const noticeViewRecordList = ref();
// 查询详情
async function queryNoticeDetail() {
try {
SmartLoading.show();
const result = await noticeApi.view(route.query.noticeId);
noticeDetail.value = result.data;
noticeViewRecordList.value.onSearch();
} catch (err) {
smartSentry.captureError(err);
} finally {
SmartLoading.hide();
}
}
});
const noticeViewRecordList = ref();
// 查询详情
async function queryNoticeDetail() {
try {
SmartLoading.show();
const result = await noticeApi.view(route.query.noticeId);
noticeDetail.value = result.data;
noticeViewRecordList.value.onSearch();
} catch (err) {
smartSentry.captureError(err);
} finally {
SmartLoading.hide();
// 点击编辑
const noticeFormDrawerRef = ref();
function onEdit() {
noticeFormDrawerRef.value.showModal(noticeDetail.value.noticeId);
}
}
// 点击编辑
const noticeFormDrawerRef = ref();
function onEdit() {
noticeFormDrawerRef.value.showModal(noticeDetail.value.noticeId);
}
// 预览附件
const filePreviewRef = ref();
function onPrevFile(fileItem) {
filePreviewRef.value.showPreview(fileItem);
}
// 打印
function print() {
let bdhtml = window.document.body.innerHTML;
let sprnstr = '<!--startprint-->'; //必须在页面添加<!--startprint-->和<!--endprint-->而且需要打印的内容必须在它们之间
let eprnstr = '<!--endprint-->';
let prnhtml = bdhtml.substr(bdhtml.indexOf(sprnstr));
prnhtml = prnhtml.substring(0, prnhtml.indexOf(eprnstr));
let newWin = window.open(''); //新打开一个空窗口
newWin.document.body.innerHTML = prnhtml;
newWin.document.close(); //在IE浏览器中使用必须添加这一句
newWin.focus(); //在IE浏览器中使用必须添加这一句
newWin.print(); //打印
newWin.close(); //关闭窗口
}
// 打印
function print() {
let bdhtml = window.document.body.innerHTML;
let sprnstr = '<!--startprint-->'; //必须在页面添加<!--startprint-->和<!--endprint-->而且需要打印的内容必须在它们之间
let eprnstr = '<!--endprint-->';
let prnhtml = bdhtml.substr(bdhtml.indexOf(sprnstr));
prnhtml = prnhtml.substring(0, prnhtml.indexOf(eprnstr));
let newWin = window.open(''); //新打开一个空窗口
newWin.document.body.innerHTML = prnhtml;
newWin.document.close(); //在IE浏览器中使用必须添加这一句
newWin.focus(); //在IE浏览器中使用必须添加这一句
newWin.print(); //打印
newWin.close(); //关闭窗口
}
</script>
<style lang="less" scoped>
:deep(.ant-descriptions-item-content) {
flex: 1;
overflow: hidden;
}
.file-list {
width: 100%;
display: flex;
flex-wrap: wrap;
.file-item {
display: block;
margin-right: 10px;
:deep(.ant-descriptions-item-content) {
flex: 1;
overflow: hidden;
}
}
.visible-list {
display: flex;
flex-wrap: wrap;
.visible-item {
margin-right: 10px;
color: #666;
}
}
.content-header {
.content-header-title {
margin: 10px 0px;
font-size: 18px;
font-weight: bold;
text-align: center;
}
.content-header-info {
margin: 10px 0px;
font-size: 14px;
color: #888;
text-align: center;
span {
margin: 0 10px;
cursor: pointer;
.file-list {
width: 100%;
display: flex;
flex-wrap: wrap;
.file-item {
display: block;
margin-right: 10px;
}
}
}
.content-html {
img {
max-width: 100%;
.visible-list {
display: flex;
flex-wrap: wrap;
.visible-item {
margin-right: 10px;
color: #666;
}
}
.content-header {
.content-header-title {
margin: 10px 0px;
font-size: 18px;
font-weight: bold;
text-align: center;
}
.content-header-info {
margin: 10px 0px;
font-size: 14px;
color: #888;
text-align: center;
span {
margin: 0 10px;
cursor: pointer;
}
}
}
.content-html {
img {
max-width: 100%;
}
}
}
</style>

View File

@@ -6,11 +6,12 @@
* @Copyright 1024创新实验室
-->
<template>
<a-modal title="更新日志" width="700px" :open="visibleFlag" @close="onClose" >
<a-modal title="更新日志" width="700px" :open="visibleFlag" @cancel="onClose">
<div>
<pre>{{ content }}</pre>
<div v-if="link">链接:<a :href="link" target="_blank">{{ link }}</a></div>
<div v-if="link">
链接<a :href="link" target="_blank">{{ link }}</a>
</div>
</div>
<template #footer>
@@ -18,7 +19,6 @@
<a-button type="primary" @click="onClose">关闭</a-button>
</a-space>
</template>
</a-modal>
</template>
<script setup>

View File

@@ -45,7 +45,7 @@
<a-table rowKey="feedbackId" :dataSource="tableData" :columns="tableColumns" :pagination="false" :loading="tableLoading" size="small" bordered>
<template #bodyCell="{ text, column }">
<template v-if="column.dataIndex === 'feedbackAttachment'">
<FilePreview :fileList="text" />
<FilePreview :fileList="text" type="picture" />
</template>
<template v-if="column.dataIndex === 'userType'">
<span>{{ $smartEnumPlugin.getDescByValue('USER_TYPE_ENUM', text) }}</span>

View File

@@ -138,13 +138,191 @@ function print() {
}
}
}
.content-html {
/*样式深入*/
:deep(.content-html) {
margin-top: 30px;
padding: 0 8px;
line-height: 28px;
font-size: 14px;
font-size: 16px;
border: #1e1e1e;
img {
max-width: 100%;
}
body {
margin: 0 auto;
color: #ccd1d8;
line-height: 1.5;
padding: 16px;
background-color: #333842;
font-size: 16px;
}
h1, h2, h3, h4, h5, h6 {
color: #0D366F;
font-weight: bold;
margin-top: 20px;
margin-bottom: 10px;
padding: 0;
}
p {
padding: 0;
margin-bottom: 16px;
}
h1 {
font-size: 26px;
}
h2 {
font-size: 24px;
}
h3 {
font-size: 22px;
}
h4 {
font-size: 20px;
}
h5 {
font-size: 19px;
}
h6 {
font-size: 18px;
}
a {
color: #61afef;
margin: 0;
padding: 0;
vertical-align: baseline;
text-decoration: none;
word-break: break-word;
}
a:hover {
text-decoration: underline;
}
a:visited {
color: #ba68c8;
}
ul, ol {
padding: 0;
padding-left: 24px;
}
li {
line-height: 24px;
}
li ul, li ol {
margin-left: 16px;
}
p, ul, ol {
font-size: 16px;
line-height: 24px;
}
mark {
color: #000000;
background-color: #c4c400;
}
pre {
display: block;
overflow-y: hidden;
overflow-x: auto;
-moz-tab-size: 4;
tab-size: 4;
}
code {
color: #98c379;
word-break: break-word;
}
pre code {
display: block;
padding-left: 0.5em;
padding-right: 0.5em;
color: #98c379;
background-color: #2d323b;
line-height: 1.5;
white-space: pre;
-moz-tab-size: 4;
tab-size: 4;
}
aside {
display: block;
float: right;
width: 390px;
}
blockquote {
color: #abb2bf;
border-left: .5em solid #abb2bf;
padding: 0 1em;
margin-left: 0;
}
blockquote p {
color: #abb2bf;
}
hr {
display: block;
text-align: left;
margin: 1em 0;
border: none;
height: 2px;
background-color: #4c5562;
}
table {
padding: 0;
margin: 1rem 0.5rem;
border-collapse: collapse;
}
table tr {
border-top: 1px solid #4c5562;
margin: 0;
padding: 0;
}
table tr:hover {
background-color: #DBE5F2;
}
table tr th {
font-weight: bold;
background-color: #90BFFF;
border: 1px solid #4c5562;
margin: 0;
padding: 6px 13px;
}
table tr td {
border: 1px solid #4c5562;
margin: 0;
padding: 6px 13px;
}
table tr th :first-child, table tr td :first-child {
margin-top: 0;
}
table tr th :last-child, table tr td :last-child {
margin-bottom: 0;
}
}
</style>

View File

@@ -16,6 +16,8 @@
@close="onClose"
destroyOnClose
>
<a-alert message="超管需要直接在数据库表 t_employee修改哦" type="error" closable />
<br />
<a-form ref="formRef" :model="form" :rules="rules" layout="vertical">
<a-form-item label="姓名" name="actualName">
<a-input v-model:value.trim="form.actualName" placeholder="请输入姓名" />
@@ -63,7 +65,7 @@
import { GENDER_ENUM } from '/@/constants/common-const';
import { regular } from '/@/constants/regular-const';
import { SmartLoading } from '/@/components/framework/smart-loading';
import { smartSentry } from '/@/lib/smart-sentry';
import { smartSentry } from '/@/lib/smart-sentry';
// ----------------------- 以下是字段定义 emits props ---------------------
const departmentTreeSelect = ref();
// emit

View File

@@ -57,6 +57,9 @@
bordered
>
<template #bodyCell="{ text, record, index, column }">
<template v-if="column.dataIndex === 'administratorFlag'">
<a-tag color="error" v-if="text">超管</a-tag>
</template>
<template v-if="column.dataIndex === 'disabledFlag'">
<a-tag :color="text ? 'error' : 'processing'">{{ text ? '禁用' : '启用' }}</a-tag>
</template>
@@ -152,6 +155,11 @@
dataIndex: 'loginName',
width: 100,
},
{
title: '超管',
dataIndex: 'administratorFlag',
width: 60,
},
{
title: '状态',
dataIndex: 'disabledFlag',

View File

@@ -12,7 +12,7 @@
<div>
<div class="btn-group">
<a-button class="button-style" type="primary" @click="updateDataScope" v-privilege="'system:role:dataScope:update'"> 保存 </a-button>
<a-button class="button-style" @click="getDataScope" > 刷新 </a-button>
<a-button class="button-style" @click="getDataScope"> 刷新 </a-button>
</div>
<a-row class="header">
<a-col class="tab-margin" :span="4">业务单据</a-col>
@@ -107,7 +107,7 @@
roleId: selectRoleId.value,
dataScopeItemList: selectedDataScopeList.value.filter((e) => !_.isUndefined(e.viewType)),
};
await roleApi.updateRoleDataScopeList(data);
await roleApi.updateDataScope(data);
message.success('保存成功');
getDataScope();
} catch (e) {

View File

@@ -9,7 +9,7 @@
*
-->
<template>
<default-home-card extra="更多" icon="FireTwoTone" title="更新日志" @extraClick="onMore">
<default-home-card extra="更多" icon="FlagOutlined" title="更新日志" @extraClick="onMore">
<a-empty v-if="$lodash.isEmpty(data)" />
<ul v-else>
<template v-for="(item, index) in data" :key="index">

View File

@@ -13,9 +13,9 @@
<a-card size="small">
<template #title>
<div class="title">
<component :is="$antIcons[props.icon]" v-if="props.icon" :style="{ fontSize: '18px' }" />
<component :is="$antIcons[props.icon]" v-if="props.icon" :style="{ fontSize: '18px', color: token.colorPrimary }" />
<slot name="title"></slot>
<span v-if="!$slots.title" class="smart-margin-left10">{{ props.title }}</span>
<span v-if="!$slots.title" class="smart-margin-left10">{{ props.title }} </span>
</div>
</template>
<template v-if="props.extra" #extra>
@@ -27,34 +27,43 @@
</div>
</template>
<script setup>
let props = defineProps({
icon: String,
title: String,
extra: String,
});
let emits = defineEmits(['extraClick']);
import { theme } from 'ant-design-vue';
import { computed } from 'vue';
function extraClick() {
emits('extraClick');
}
let props = defineProps({
icon: String,
title: String,
extra: String,
});
let emits = defineEmits(['extraClick']);
function extraClick() {
emits('extraClick');
}
const { useToken } = theme;
const { token } = useToken();
const color = computed(() => {
return token.colorPrimary;
});
</script>
<style lang="less" scoped>
.card-container {
background-color: #fff;
height: 100%;
.card-container {
background-color: #fff;
height: 100%;
.title {
display: flex;
align-items: center;
&::before {
content: '';
position: absolute;
top: 3px;
left: 0;
width: 3px;
height: 30px;
background-color: @primary-color;
.title {
display: flex;
align-items: center;
&::before {
content: '';
position: absolute;
top: 3px;
left: 0;
width: 3px;
height: 30px;
background-color: v-bind('token.colorPrimary');
}
}
}
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<default-home-card icon="ProfileTwoTone" title="销量统计">
<default-home-card icon="Profile" title="销量统计">
<div class="echarts-box">
<div class="category-main" id="category-main"></div>
</div>

View File

@@ -7,113 +7,112 @@
* @FilePath: /smart-admin/src/views/system/home/components/gauge.vue
-->
<template>
<default-home-card icon="RocketTwoTone" title="业绩完成度">
<default-home-card icon="Rocket" title="业绩完成度">
<div class="echarts-box">
<div id="gauge-main" class="gauge-main"></div>
</div>
</default-home-card>
</template>
<script setup>
import DefaultHomeCard from "/@/views/system/home/components/default-home-card.vue";
import * as echarts from "echarts";
import {onMounted, watch} from "vue";
import {reactive} from "vue";
import DefaultHomeCard from '/@/views/system/home/components/default-home-card.vue';
import * as echarts from 'echarts';
import { onMounted, watch } from 'vue';
import { reactive } from 'vue';
const props = defineProps({
percent: {
type: Number,
default: 0
},
});
const props = defineProps({
percent: {
type: Number,
default: 0,
},
});
let option = reactive({});
watch(
let option = reactive({});
watch(
() => props.percent,
() => {
init();
}
);
onMounted(() => {
init();
});
);
onMounted(() => {
init();
});
function init() {
option = {
series: [
{
type: "gauge",
startAngle: 90,
endAngle: -270,
pointer: {
show: false,
},
progress: {
show: true,
overlap: false,
roundCap: true,
clip: false,
itemStyle: {
borderWidth: 1,
borderColor: "#464646",
function init() {
option = {
series: [
{
type: 'gauge',
startAngle: 90,
endAngle: -270,
pointer: {
show: false,
},
},
axisLine: {
lineStyle: {
width: 20,
},
},
splitLine: {
show: false,
distance: 0,
length: 10,
},
axisTick: {
show: false,
},
axisLabel: {
show: false,
distance: 50,
},
data: [
{
value: props.percent,
name: "完成度",
title: {
offsetCenter: ["0%", "-10%"],
},
detail: {
offsetCenter: ["0%", "20%"],
progress: {
show: true,
overlap: false,
roundCap: true,
clip: false,
itemStyle: {
borderWidth: 1,
borderColor: '#464646',
},
},
],
title: {
fontSize: 18,
axisLine: {
lineStyle: {
width: 20,
},
},
splitLine: {
show: false,
distance: 0,
length: 10,
},
axisTick: {
show: false,
},
axisLabel: {
show: false,
distance: 50,
},
data: [
{
value: props.percent,
name: '完成度',
title: {
offsetCenter: ['0%', '-10%'],
},
detail: {
offsetCenter: ['0%', '20%'],
},
},
],
title: {
fontSize: 18,
},
detail: {
fontSize: 16,
color: 'auto',
formatter: '{value}%',
},
},
detail: {
fontSize: 16,
color: "auto",
formatter: "{value}%",
},
},
],
};
let chartDom = document.getElementById("gauge-main");
if (chartDom) {
let myChart = echarts.init(chartDom);
option && myChart.setOption(option);
],
};
let chartDom = document.getElementById('gauge-main');
if (chartDom) {
let myChart = echarts.init(chartDom);
option && myChart.setOption(option);
}
}
}
</script>
<style lang="less" scoped>
.echarts-box {
display: flex;
align-items: center;
justify-content: center;
.gauge-main {
width: 260px;
height: 260px;
background: #fff;
.echarts-box {
display: flex;
align-items: center;
justify-content: center;
.gauge-main {
width: 260px;
height: 260px;
background: #fff;
}
}
}
</style>

View File

@@ -1,210 +1,210 @@
<template>
<default-home-card icon="FundTwoTone" title="代码提交量">
<default-home-card icon="BarChartOutlined" title="代码提交量">
<div class="echarts-box">
<div class="gradient-main" id="gradient-main"></div>
</div>
</default-home-card>
</template>
<script setup>
import DefaultHomeCard from "/@/views/system/home/components/default-home-card.vue";
import * as echarts from 'echarts';
import {onMounted} from "vue";
import DefaultHomeCard from '/@/views/system/home/components/default-home-card.vue';
import * as echarts from 'echarts';
import { onMounted } from 'vue';
onMounted(() => {
init();
});
onMounted(() => {
init();
});
function init(){
let option = {
color: ['#80FFA5', '#00DDFF', '#37A2FF', '#FF0087', '#FFBF00'],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985'
}
}
},
legend: {
data: ['罗伊', '佩弦', '开云', '清野', '飞叶']
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: [
{
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
}
],
yAxis: [
{
type: 'value'
}
],
series: [
{
name: '罗伊',
type: 'line',
stack: 'Total',
smooth: true,
lineStyle: {
width: 0
function init() {
let option = {
color: ['#80FFA5', '#00DDFF', '#37A2FF', '#FF0087', '#FFBF00'],
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
label: {
backgroundColor: '#6a7985',
},
},
showSymbol: false,
areaStyle: {
opacity: 0.8,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgb(128, 255, 165)'
},
{
offset: 1,
color: 'rgb(1, 191, 236)'
}
])
},
emphasis: {
focus: 'series'
},
data: [140, 232, 101, 264, 90, 340, 250]
},
{
name: '佩弦',
type: 'line',
stack: 'Total',
smooth: true,
lineStyle: {
width: 0
},
showSymbol: false,
areaStyle: {
opacity: 0.8,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgb(0, 221, 255)'
},
{
offset: 1,
color: 'rgb(77, 119, 255)'
}
])
},
emphasis: {
focus: 'series'
},
data: [120, 282, 111, 234, 220, 340, 310]
legend: {
data: ['罗伊', '佩弦', '开云', '清野', '飞叶'],
},
{
name: '开云',
type: 'line',
stack: 'Total',
smooth: true,
lineStyle: {
width: 0
},
showSymbol: false,
areaStyle: {
opacity: 0.8,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgb(55, 162, 255)'
},
{
offset: 1,
color: 'rgb(116, 21, 219)'
}
])
},
emphasis: {
focus: 'series'
},
data: [320, 132, 201, 334, 190, 130, 220]
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true,
},
{
name: '清野',
type: 'line',
stack: 'Total',
smooth: true,
lineStyle: {
width: 0
xAxis: [
{
type: 'category',
boundaryGap: false,
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
},
showSymbol: false,
areaStyle: {
opacity: 0.8,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgb(255, 0, 135)'
},
{
offset: 1,
color: 'rgb(135, 0, 157)'
}
])
],
yAxis: [
{
type: 'value',
},
emphasis: {
focus: 'series'
],
series: [
{
name: '罗伊',
type: 'line',
stack: 'Total',
smooth: true,
lineStyle: {
width: 0,
},
showSymbol: false,
areaStyle: {
opacity: 0.8,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgb(128, 255, 165)',
},
{
offset: 1,
color: 'rgb(1, 191, 236)',
},
]),
},
emphasis: {
focus: 'series',
},
data: [140, 232, 101, 264, 90, 340, 250],
},
data: [220, 402, 231, 134, 190, 230, 120]
},
{
name: '飞叶',
type: 'line',
stack: 'Total',
smooth: true,
lineStyle: {
width: 0
{
name: '佩弦',
type: 'line',
stack: 'Total',
smooth: true,
lineStyle: {
width: 0,
},
showSymbol: false,
areaStyle: {
opacity: 0.8,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgb(0, 221, 255)',
},
{
offset: 1,
color: 'rgb(77, 119, 255)',
},
]),
},
emphasis: {
focus: 'series',
},
data: [120, 282, 111, 234, 220, 340, 310],
},
showSymbol: false,
label: {
show: true,
position: 'top'
{
name: '开云',
type: 'line',
stack: 'Total',
smooth: true,
lineStyle: {
width: 0,
},
showSymbol: false,
areaStyle: {
opacity: 0.8,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgb(55, 162, 255)',
},
{
offset: 1,
color: 'rgb(116, 21, 219)',
},
]),
},
emphasis: {
focus: 'series',
},
data: [320, 132, 201, 334, 190, 130, 220],
},
areaStyle: {
opacity: 0.8,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgb(255, 191, 0)'
},
{
offset: 1,
color: 'rgb(224, 62, 76)'
}
])
{
name: '清野',
type: 'line',
stack: 'Total',
smooth: true,
lineStyle: {
width: 0,
},
showSymbol: false,
areaStyle: {
opacity: 0.8,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgb(255, 0, 135)',
},
{
offset: 1,
color: 'rgb(135, 0, 157)',
},
]),
},
emphasis: {
focus: 'series',
},
data: [220, 402, 231, 134, 190, 230, 120],
},
emphasis: {
focus: 'series'
{
name: '飞叶',
type: 'line',
stack: 'Total',
smooth: true,
lineStyle: {
width: 0,
},
showSymbol: false,
label: {
show: true,
position: 'top',
},
areaStyle: {
opacity: 0.8,
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgb(255, 191, 0)',
},
{
offset: 1,
color: 'rgb(224, 62, 76)',
},
]),
},
emphasis: {
focus: 'series',
},
data: [220, 302, 181, 234, 210, 290, 150],
},
data: [220, 302, 181, 234, 210, 290, 150]
}
]
};
let chartDom = document.getElementById("gradient-main");
if (chartDom) {
let myChart = echarts.init(chartDom);
option && myChart.setOption(option);
],
};
let chartDom = document.getElementById('gradient-main');
if (chartDom) {
let myChart = echarts.init(chartDom);
option && myChart.setOption(option);
}
}
}
</script>
<style lang='less' scoped>
.echarts-box {
display: flex;
align-items: center;
justify-content: center;
.gradient-main {
width: 1200px;
height: 300px;
background: #fff;
<style lang="less" scoped>
.echarts-box {
display: flex;
align-items: center;
justify-content: center;
.gradient-main {
width: 1200px;
height: 300px;
background: #fff;
}
}
}
</style>

View File

@@ -1,78 +1,78 @@
<template>
<default-home-card icon="PieChartTwoTone" title="加班统计">
<default-home-card icon="PieChartOutlined" title="加班统计">
<div class="echarts-box">
<div class="pie-main" id="pie-main"></div>
</div>
</default-home-card>
</template>
<script setup>
import DefaultHomeCard from "/@/views/system/home/components/default-home-card.vue";
import * as echarts from 'echarts';
import {onMounted} from "vue";
import DefaultHomeCard from '/@/views/system/home/components/default-home-card.vue';
import * as echarts from 'echarts';
import { onMounted } from 'vue';
onMounted(() => {
init();
});
onMounted(() => {
init();
});
function init(){
let option = {
tooltip: {
trigger: 'item'
},
legend: {
top: '5%',
left: 'center'
},
series: [
{
name: '加班次数',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false,
position: 'center'
},
emphasis: {
function init() {
let option = {
tooltip: {
trigger: 'item',
},
legend: {
top: '5%',
left: 'center',
},
series: [
{
name: '加班次数',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2,
},
label: {
show: true,
fontSize: '40',
fontWeight: 'bold'
}
show: false,
position: 'center',
},
emphasis: {
label: {
show: true,
fontSize: '40',
fontWeight: 'bold',
},
},
labelLine: {
show: false,
},
data: [
{ value: 10, name: '初晓' },
{ value: 8, name: '善逸' },
{ value: 3, name: '胡克' },
{ value: 1, name: '罗伊' },
],
},
labelLine: {
show: false
},
data: [
{ value: 10, name: '初晓' },
{ value: 8, name: '善逸' },
{ value: 3, name: '胡克' },
{ value: 1, name: '罗伊' },
]
}
]
};
let chartDom = document.getElementById("pie-main");
if (chartDom) {
let myChart = echarts.init(chartDom);
option && myChart.setOption(option);
],
};
let chartDom = document.getElementById('pie-main');
if (chartDom) {
let myChart = echarts.init(chartDom);
option && myChart.setOption(option);
}
}
}
</script>
<style lang='less' scoped>
.echarts-box {
display: flex;
align-items: center;
justify-content: center;
.pie-main {
width: 260px;
height: 260px;
background: #fff;
<style lang="less" scoped>
.echarts-box {
display: flex;
align-items: center;
justify-content: center;
.pie-main {
width: 260px;
height: 260px;
background: #fff;
}
}
}
</style>

View File

@@ -9,7 +9,7 @@
*
-->
<template>
<default-home-card icon="SmileTwoTone" title="联系我们">
<default-home-card icon="SmileOutlined" title="联系我们">
<div class="app-qr-box">
<div class="app-qr">
<img :src="zhuoda" />

View File

@@ -1,17 +1,12 @@
<template>
<default-home-card
:extra="`${editFlag ? '完成' : '编辑'}`"
icon="ThunderboltTwoTone"
title="快捷入口"
@extraClick="editFlag = !editFlag"
>
<default-home-card :extra="`${editFlag ? '完成' : '编辑'}`" icon="ThunderboltTwoTone" title="快捷入口" @extraClick="editFlag = !editFlag">
<div class="quick-entry-list">
<a-row>
<a-col v-for="(item,index) in quickEntry" :key="index" span="4">
<a-col v-for="(item, index) in quickEntry" :key="index" span="4">
<div class="quick-entry" @click="turnToPage(item.path)">
<div class="icon">
<component :is='$antIcons[item.icon]' :style="{ fontSize:'30px'}"/>
<close-circle-outlined v-if="editFlag" class="delete-icon" @click="deleteQuickEntry(index)"/>
<component :is="$antIcons[item.icon]" :style="{ fontSize: '30px' }" />
<close-circle-outlined v-if="editFlag" class="delete-icon" @click="deleteQuickEntry(index)" />
</div>
<span class="entry-title">{{ item.title }}</span>
</div>
@@ -19,131 +14,135 @@
<a-col v-if="editFlag && quickEntry.length < maxCount" span="4">
<div class="add-quick-entry" @click="addHomeQuickEntry">
<div class="add-icon">
<plus-outlined :style="{ fontSize:'30px'}"/>
<plus-outlined :style="{ fontSize: '30px' }" />
</div>
</div>
</a-col>
</a-row>
</div>
</default-home-card>
<HomeQuickEntryModal ref="homeQuickEntryModal" @addQuickEntry="addQuickEntry"/>
<HomeQuickEntryModal ref="homeQuickEntryModal" @addQuickEntry="addQuickEntry" />
</template>
<script setup>
import {onMounted, ref} from "vue";
import {router} from "/@/router";
import HomeQuickEntryModal from './home-quick-entry-modal.vue'
import localKey from '/@/constants/local-storage-key-const';
import {localRead, localSave} from '/@/utils/local-util';
import _ from "lodash";
import InitQuickEntryList from './init-quick-entry-list';
import DefaultHomeCard from "/@/views/system/home/components/default-home-card.vue";
import { onMounted, ref } from 'vue';
import { router } from '/@/router';
import HomeQuickEntryModal from './home-quick-entry-modal.vue';
import localKey from '/@/constants/local-storage-key-const';
import { localRead, localSave } from '/@/utils/local-util';
import _ from 'lodash';
import InitQuickEntryList from './init-quick-entry-list';
import DefaultHomeCard from '/@/views/system/home/components/default-home-card.vue';
import { theme } from 'ant-design-vue';
//---------------- 初始化展示 --------------------
onMounted(() => {
initQuickEntry();
})
let quickEntry = ref([])
//---------------- 初始化展示 --------------------
onMounted(() => {
initQuickEntry();
});
let quickEntry = ref([]);
function initQuickEntry() {
let quickEntryJson = localRead(localKey.HOME_QUICK_ENTRY);
if (!quickEntryJson) {
quickEntry.value = _.cloneDeep(InitQuickEntryList);
return;
function initQuickEntry() {
let quickEntryJson = localRead(localKey.HOME_QUICK_ENTRY);
if (!quickEntryJson) {
quickEntry.value = _.cloneDeep(InitQuickEntryList);
return;
}
let quickEntryList = JSON.parse(quickEntryJson);
if (_.isEmpty(quickEntryList)) {
quickEntry.value = _.cloneDeep(InitQuickEntryList);
return;
}
quickEntry.value = quickEntryList;
}
let quickEntryList = JSON.parse(quickEntryJson);
if (_.isEmpty(quickEntryList)) {
quickEntry.value = _.cloneDeep(InitQuickEntryList);
return;
// 页面跳转
function turnToPage(path) {
if (editFlag.value) {
return;
}
router.push({ path });
}
quickEntry.value = quickEntryList;
}
// 页面跳转
function turnToPage(path) {
if (editFlag.value) {
return;
//---------------- 编辑快捷入口 --------------------
let editFlag = ref(false);
let maxCount = ref(6);
// 快捷入口删除
function deleteQuickEntry(index) {
quickEntry.value.splice(index, 1);
localSave(localKey.HOME_QUICK_ENTRY, JSON.stringify(quickEntry.value));
}
router.push({path});
}
//---------------- 编辑快捷入口 --------------------
let editFlag = ref(false);
let maxCount = ref(6);
// 添加快捷入口
let homeQuickEntryModal = ref();
// 快捷入口删除
function deleteQuickEntry(index) {
quickEntry.value.splice(index, 1)
localSave(localKey.HOME_QUICK_ENTRY, JSON.stringify(quickEntry.value));
}
function addHomeQuickEntry() {
homeQuickEntryModal.value.showModal();
}
// 添加快捷入口
let homeQuickEntryModal = ref();
function addQuickEntry(row) {
quickEntry.value.push(row);
localSave(localKey.HOME_QUICK_ENTRY, JSON.stringify(quickEntry.value));
}
function addHomeQuickEntry() {
homeQuickEntryModal.value.showModal();
}
function addQuickEntry(row) {
quickEntry.value.push(row);
localSave(localKey.HOME_QUICK_ENTRY, JSON.stringify(quickEntry.value));
}
const { useToken } = theme;
const { token } = useToken();
</script>
<style lang='less' scoped>
.quick-entry-list {
height: 100%;
<style lang="less" scoped>
.quick-entry-list {
height: 100%;
.quick-entry {
padding: 10px 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
border-radius: 4px;
.entry-title {
margin-top: 5px;
}
.icon {
position: relative;
}
&:hover {
background-color: #F0FFFF;
}
.delete-icon {
position: absolute;
color: #F08080;
top: -5px;
right: -5px;
}
}
.add-quick-entry {
display: flex;
align-items: center;
justify-content: center;
.add-icon {
width: 70px;
height: 70px;
background-color: #fafafa;
border: 1px dashed #d9d9d9;
border-radius: 2px;
.quick-entry {
padding: 10px 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: pointer;
transition: border-color .3s;
border-radius: 4px;
.entry-title {
margin-top: 5px;
}
.icon {
position: relative;
}
&:hover {
background-color: #f0ffff;
}
.delete-icon {
position: absolute;
color: #f08080;
top: -5px;
right: -5px;
}
}
.add-quick-entry {
display: flex;
align-items: center;
justify-content: center;
color: #A9A9A9;
&:hover {
border-color: @primary-color;
color: @primary-color;
.add-icon {
width: 70px;
height: 70px;
background-color: #fafafa;
border: 1px dashed #d9d9d9;
border-radius: 2px;
cursor: pointer;
transition: border-color 0.3s;
display: flex;
align-items: center;
justify-content: center;
color: #a9a9a9;
&:hover {
border-color: v-bind('token.colorPrimary');
color: v-bind('token.colorPrimary');
}
}
}
}
}
</style>

View File

@@ -9,8 +9,8 @@
*
-->
<template>
<default-home-card icon="StarTwoTone" title="已办待办">
<div style="height: 280px;">
<default-home-card icon="Star" title="已办待办">
<div style="height: 280px">
<div class="center column">
<a-space direction="vertical" style="width: 100%">
<div v-for="(item, index) in toDoList" :key="index" :class="['to-do', { done: item.doneFlag }]">

View File

@@ -9,7 +9,7 @@
*
-->
<template>
<default-home-card extra="更多" icon="SoundTwoTone" title="通知公告" @extraClick="onMore">
<default-home-card extra="更多" icon="SoundOutlined" title="通知公告" @extraClick="onMore">
<a-spin :spinning="loading">
<div class="content-wrapper">
<a-empty v-if="$lodash.isEmpty(data)" />
@@ -37,10 +37,6 @@
import { noticeApi } from '/@/api/business/oa/notice-api';
import { smartSentry } from '/@/lib/smart-sentry';
import DefaultHomeCard from '/@/views/system/home/components/default-home-card.vue';
import { theme } from 'ant-design-vue';
const { useToken } = theme;
const { token } = useToken();
const colorPrimary = token.value.colorPrimary;
const props = defineProps({
noticeTypeId: {
@@ -110,7 +106,6 @@
overflow: hidden;
word-break: break-all;
margin-right: 5px;
color: v-bind(colorPrimary);
}
.time {

View File

@@ -1,7 +1,7 @@
.login-container {
width: 100%;
height: 100%;
background: url(/@/assets/images/login/login-bg.jpg) no-repeat center;
background: url(/@/assets/images/login/login-bg.png) no-repeat center;
background-size: cover;
display: flex;
align-items: center;
@@ -20,6 +20,7 @@
border-radius: 0px 12px 12px 0px;
padding: 34px 42px;
position: relative;
border: solid 1px #efefef;
}
.login-qr {
position: absolute;
@@ -160,7 +161,7 @@
.btn {
width: 350px;
height: 50px;
background: #1890ff;
background: #1748FD;
border-radius: 4px;
font-size: 16px;
font-weight: 700;
@@ -193,14 +194,14 @@
margin: 0 19px;
}
.login-type {
padding: 0 50px;
padding: 0 5px;
margin-top: 25px;
display: flex;
align-items: center;
justify-content: space-between;
> img {
width: 22px;
height: 22px;
width: 30px;
height: 30px;
}
}
}

View File

@@ -1,11 +1,11 @@
<!--
* 登录
*
* @Author: 1024创新实验室-主任卓大
* @Date: 2022-09-12 22:34:00
* @Wechat: zhuda1024
* @Email: lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net Since 2012
*
* @Author: 1024创新实验室-主任卓大
* @Date: 2022-09-12 22:34:00
* @Wechat: zhuda1024
* @Email: lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net Since 2012
*
-->
<template>
@@ -86,38 +86,43 @@
<p class="line"></p>
</div>
<div class="login-type">
<img :src="aliLogin" />
<img :src="qqLogin" />
<img :src="googleLogin" />
<img :src="weiboLogin" />
<img :src="wechatIcon" />
<img :src="aliIcon" />
<img :src="douyinIcon" />
<img :src="qqIcon" />
<img :src="weiboIcon" />
<img :src="feishuIcon" />
<img :src="googleIcon" />
</div>
</div>
</div>
</div>
</template>
<script setup>
import { message } from 'ant-design-vue';
import { message, notification, Button } from 'ant-design-vue';
import { onMounted, onUnmounted, reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
import { loginApi } from '/@/api/system/login-api';
import { SmartLoading } from '/@/components/framework/smart-loading';
import { LOGIN_DEVICE_ENUM } from '/@/constants/system/login-device-const';
import { useUserStore } from '/@/store/modules/system/user';
import { saveTokenToCookie } from '/@/utils/cookie-util';
import gongzhonghao from '/@/assets/images/1024lab/1024lab-gzh.jpg';
import zhuoda from '/@/assets/images/1024lab/zhuoda-wechat.jpg';
import loginQR from '/@/assets/images/login/login-qr.png';
import gzh from '/@/assets/images/1024lab/gzh.jpg';
import aliLogin from '/@/assets/images/login/ali-icon.png';
import googleLogin from '/@/assets/images/login/google-icon.png';
import qqLogin from '/@/assets/images/login/qq-icon.png';
import weiboLogin from '/@/assets/images/login/weibo-icon.png';
import wechatIcon from '/@/assets/images/login/wechat-icon.png';
import aliIcon from '/@/assets/images/login/ali-icon.png';
import douyinIcon from '/@/assets/images/login/douyin-icon.png';
import qqIcon from '/@/assets/images/login/qq-icon.png';
import weiboIcon from '/@/assets/images/login/weibo-icon.png';
import feishuIcon from '/@/assets/images/login/feishu-icon.png';
import googleIcon from '/@/assets/images/login/google-icon.png';
import { buildRoutes } from '/@/router/index';
import { smartSentry } from '/@/lib/smart-sentry';
import { encryptData } from '/@/lib/encrypt';
import { h } from 'vue';
import { localSave } from '/@/utils/local-util.js';
import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
//--------------------- 登录表单 ---------------------------------
@@ -145,6 +150,25 @@
onLogin();
}
};
notification['success']({
message: '温馨提示',
description: 'SmartAdmin 提供 9种 登录背景风格哦!',
duration: 8,
onClick: () => {},
btn: () =>
h(
Button,
{
type: 'primary',
target: '_blank',
size: 'small',
href: 'https://smartadmin.vip/views/v3/front/Login.html',
onClick: () => {},
},
{ default: () => '去看看' }
),
});
});
onUnmounted(() => {
@@ -162,7 +186,7 @@
});
const res = await loginApi.login(encryptPasswordForm);
stopRefrestCaptchaInterval();
saveTokenToCookie(res.data.token ? res.data.token : '');
localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '');
message.success('登录成功');
//更新用户信息到pinia
useUserStore().setUserLoginInfo(res.data);

View File

@@ -0,0 +1,196 @@
.login-container {
width: 100%;
height: 100%;
background: url(/@/assets/images/login/login-bg.png) no-repeat center;
background-size: cover;
display: flex;
align-items: center;
justify-content: center;
.box-item {
width: 444px;
height: 570px;
&.desc {
background: #ffffff;
border-radius: 12px 0px 0px 12px;
box-shadow: 0px 16px 73px 8px rgba(203, 203, 203, 0.2);
padding: 23px 25px;
}
&.login {
background: #ffffff;
border-radius: 0px 12px 12px 0px;
padding: 34px 42px;
position: relative;
}
.login-qr {
position: absolute;
top: 0;
right: 0;
width: 66px;
height: 66px;
}
.welcome {
background: url(/@/assets/images/login/left-bg1.png) no-repeat center;
background-size: cover;
height: 100%;
border-radius: 8px;
box-shadow: 0px 6px 20px 0px rgba(33,47,117,0.10);
padding-top: 35px;
p{
color: #333333;
line-height: 25px;
letter-spacing: 0.26px;
text-align: center;
font-weight: 700;
font-size: 26px;
}
.sub-welcome{
color: #333333;
line-height: 25px;
letter-spacing: 0.26px;
font-weight: 700;
font-size: 20px;
}
}
.app-qr-box {
display: flex;
align-items: center;
justify-content: space-around;
margin-top: 20px;
.app-qr {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
width: 50%;
> img {
width: 112px;
height: 112px;
}
.qr-desc {
display: flex;
align-items: center;
margin-top: 11px;
font-size: 12px;
font-weight: 500;
text-align: center;
color: #ffffff;
> img {
width: 15px;
height: 18px;
margin-right: 9px;
}
}
.qr-desc-marquee {
width: 100%;
display: flex;
align-items: center;
margin-top: 11px;
font-size: 12px;
font-weight: 500;
text-align: center;
color: #ffffff;
@keyframes marquee {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-30%);
}
}
.marquee {
flex: 1;
overflow: hidden;
span {
display: inline-block;
width: 100%;
white-space: nowrap;
animation: marquee 5s linear infinite;
}
}
}
}
}
.login-title {
font-size: 20px;
text-align: center;
color: #1e1e1e;
margin-bottom: 35px;
}
.login-form {
.captcha-input {
width: 60%;
}
.captcha-img {
cursor: pointer;
}
}
.ant-input,
.ant-input-affix-wrapper {
height: 44px;
border: 1px solid #ededed;
border-radius: 4px;
}
.eye-box {
position: absolute;
right: 15px;
top: 10px;
.eye-icon {
width: 20px;
height: 20px;
cursor: pointer;
}
}
.btn {
width: 350px;
height: 50px;
background: #1748FD;
border-radius: 4px;
font-size: 16px;
font-weight: 700;
text-align: center;
color: #ffffff;
line-height: 50px;
cursor: pointer;
}
}
.more {
margin-top: 30px;
.title-box {
display: flex;
align-items: center;
justify-content: center;
> p {
margin-bottom: 0;
}
}
.line {
width: 114px;
height: 1px;
background: #e6e6e6;
}
.title {
font-size: 14px;
font-weight: 500;
color: #a1aebe;
margin: 0 19px;
}
.login-type {
padding: 0 5px;
margin-top: 25px;
display: flex;
align-items: center;
justify-content: space-between;
> img {
width: 30px;
height: 30px;
}
}
}
}

View File

@@ -0,0 +1,182 @@
<!--
* 登录
*
* @Author: 1024创新实验室-主任卓大
* @Date: 2022-09-12 22:34:00
* @Wechat: zhuda1024
* @Email: lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net Since 2012
*
-->
<template>
<div class="login-container">
<div class="box-item desc">
<div class="welcome">
<p>欢迎登录 SmartAdmin V3</p>
<p class="sub-welcome">高质量代码的快速开发平台</p>
</div>
</div>
<div class="box-item login">
<img class="login-qr" :src="loginQR" />
<div class="login-title">账号登录</div>
<a-form ref="formRef" class="login-form" :model="loginForm" :rules="rules">
<a-form-item name="loginName">
<a-input v-model:value.trim="loginForm.loginName" placeholder="请输入用户名" />
</a-form-item>
<a-form-item name="password">
<a-input-password
v-model:value="loginForm.password"
autocomplete="on"
:type="showPassword ? 'text' : 'password'"
placeholder="请输入密码:至少三种字符,最小 8 位"
/>
</a-form-item>
<a-form-item name="captchaCode">
<a-input class="captcha-input" v-model:value.trim="loginForm.captchaCode" placeholder="请输入验证码" />
<img class="captcha-img" :src="captchaBase64Image" @click="getCaptcha" />
</a-form-item>
<a-form-item>
<a-checkbox v-model:checked="rememberPwd">记住密码</a-checkbox>
<span> ( 账号admin, 密码123456)</span>
</a-form-item>
<a-form-item>
<div class="btn" @click="onLogin">登录</div>
</a-form-item>
</a-form>
<div class="more">
<div class="title-box">
<p class="line"></p>
<p class="title">其他方式登录</p>
<p class="line"></p>
</div>
<div class="login-type">
<img :src="wechatIcon" />
<img :src="aliIcon" />
<img :src="douyinIcon" />
<img :src="qqIcon" />
<img :src="weiboIcon" />
<img :src="feishuIcon" />
<img :src="googleIcon" />
</div>
</div>
</div>
</div>
</template>
<script setup>
import { message } from 'ant-design-vue';
import { onMounted, onUnmounted, reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
import { loginApi } from '/@/api/system/login-api';
import { SmartLoading } from '/@/components/framework/smart-loading';
import { LOGIN_DEVICE_ENUM } from '/@/constants/system/login-device-const';
import { useUserStore } from '/@/store/modules/system/user';
import loginQR from '/@/assets/images/login/login-qr.png';
import wechatIcon from '/@/assets/images/login/wechat-icon.png';
import aliIcon from '/@/assets/images/login/ali-icon.png';
import douyinIcon from '/@/assets/images/login/douyin-icon.png';
import qqIcon from '/@/assets/images/login/qq-icon.png';
import weiboIcon from '/@/assets/images/login/weibo-icon.png';
import feishuIcon from '/@/assets/images/login/feishu-icon.png';
import googleIcon from '/@/assets/images/login/google-icon.png';
import { buildRoutes } from '/@/router/index';
import { smartSentry } from '/@/lib/smart-sentry';
import { encryptData } from '/@/lib/encrypt';
import { localSave } from '/@/utils/local-util.js';
import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
//--------------------- 登录表单 ---------------------------------
const loginForm = reactive({
loginName: 'admin',
password: '',
captchaCode: '',
captchaUuid: '',
loginDevice: LOGIN_DEVICE_ENUM.PC.value,
});
const rules = {
loginName: [{ required: true, message: '用户名不能为空' }],
password: [{ required: true, message: '密码不能为空' }],
captchaCode: [{ required: true, message: '验证码不能为空' }],
};
const showPassword = ref(false);
const router = useRouter();
const formRef = ref();
const rememberPwd = ref(false);
onMounted(() => {
document.onkeyup = (e) => {
if (e.keyCode == 13) {
onLogin();
}
};
});
onUnmounted(() => {
document.onkeyup = null;
});
//登录
async function onLogin() {
formRef.value.validate().then(async () => {
try {
SmartLoading.show();
// 密码加密
let encryptPasswordForm = Object.assign({}, loginForm, {
password: encryptData(loginForm.password),
});
const res = await loginApi.login(encryptPasswordForm);
stopRefrestCaptchaInterval();
localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '');
message.success('登录成功');
//更新用户信息到pinia
useUserStore().setUserLoginInfo(res.data);
//构建系统的路由
buildRoutes();
router.push('/home');
} catch (e) {
if (e.data && e.data.code !== 0) {
loginForm.captchaCode = '';
getCaptcha();
}
smartSentry.captureError(e);
} finally {
SmartLoading.hide();
}
});
}
//--------------------- 验证码 ---------------------------------
const captchaBase64Image = ref('');
async function getCaptcha() {
try {
let captchaResult = await loginApi.getCaptcha();
captchaBase64Image.value = captchaResult.data.captchaBase64Image;
loginForm.captchaUuid = captchaResult.data.captchaUuid;
beginRefrestCaptchaInterval(captchaResult.data.expireSeconds);
} catch (e) {
console.log(e);
}
}
let refrestCaptchaInterval = null;
function beginRefrestCaptchaInterval(expireSeconds) {
if (refrestCaptchaInterval === null) {
refrestCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
}
}
function stopRefrestCaptchaInterval() {
if (refrestCaptchaInterval != null) {
clearInterval(refrestCaptchaInterval);
refrestCaptchaInterval = null;
}
}
onMounted(getCaptcha);
</script>
<style lang="less" scoped>
@import './login.less';
</style>

View File

@@ -0,0 +1,204 @@
.login-container {
width: 100%;
height: 100%;
background: url(/@/assets/images/login/login-bg.png) no-repeat center;
background-size: cover;
display: flex;
align-items: center;
justify-content: center;
.box-item {
width: 444px;
height: 570px;
&.desc {
background: #ffffff;
border-radius: 12px 0px 0px 12px;
box-shadow: 0px 16px 73px 8px rgba(203, 203, 203, 0.2);
padding: 23px 25px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
}
&.login {
background: #ffffff;
border-radius: 0px 12px 12px 0px;
padding: 34px 42px;
position: relative;
}
.login-qr {
position: absolute;
top: 0;
right: 0;
width: 66px;
height: 66px;
}
.welcome {
padding-top: 35px;
p{
color: #333333;
line-height: 25px;
letter-spacing: 0.26px;
text-align: center;
font-weight: 700;
font-size: 26px;
}
.sub-welcome{
padding: 10px;
font-size: 18px;
font-weight: 700;
text-align: center;
color: #ffffff;
line-height: 25px;
letter-spacing: 0.26px;
opacity: 0.96;
background: #1748fd;
border-radius: 22px 22px 2px 22px;
}
}
.welcome-img{
width: 350px;
height: 350px;
}
.app-qr-box {
display: flex;
align-items: center;
justify-content: space-around;
margin-top: 20px;
.app-qr {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
width: 50%;
> img {
width: 112px;
height: 112px;
}
.qr-desc {
display: flex;
align-items: center;
margin-top: 11px;
font-size: 12px;
font-weight: 500;
text-align: center;
color: #ffffff;
> img {
width: 15px;
height: 18px;
margin-right: 9px;
}
}
.qr-desc-marquee {
width: 100%;
display: flex;
align-items: center;
margin-top: 11px;
font-size: 12px;
font-weight: 500;
text-align: center;
color: #ffffff;
@keyframes marquee {
0% {
transform: translateX(0);
}
100% {
transform: translateX(-30%);
}
}
.marquee {
flex: 1;
overflow: hidden;
span {
display: inline-block;
width: 100%;
white-space: nowrap;
animation: marquee 5s linear infinite;
}
}
}
}
}
.login-title {
font-size: 20px;
text-align: center;
color: #1e1e1e;
margin-bottom: 35px;
}
.login-form {
.captcha-input {
width: 60%;
}
.captcha-img {
cursor: pointer;
}
}
.ant-input,
.ant-input-affix-wrapper {
height: 44px;
border: 1px solid #ededed;
border-radius: 4px;
}
.eye-box {
position: absolute;
right: 15px;
top: 10px;
.eye-icon {
width: 20px;
height: 20px;
cursor: pointer;
}
}
.btn {
width: 350px;
height: 50px;
background: #1748FD;
border-radius: 4px;
font-size: 16px;
font-weight: 700;
text-align: center;
color: #ffffff;
line-height: 50px;
cursor: pointer;
}
}
.more {
margin-top: 30px;
.title-box {
display: flex;
align-items: center;
justify-content: center;
> p {
margin-bottom: 0;
}
}
.line {
width: 114px;
height: 1px;
background: #e6e6e6;
}
.title {
font-size: 14px;
font-weight: 500;
color: #a1aebe;
margin: 0 19px;
}
.login-type {
padding: 0 5px;
margin-top: 25px;
display: flex;
align-items: center;
justify-content: space-between;
> img {
width: 30px;
height: 30px;
}
}
}
}

View File

@@ -0,0 +1,184 @@
<!--
* 登录
*
* @Author: 1024创新实验室-主任卓大
* @Date: 2022-09-12 22:34:00
* @Wechat: zhuda1024
* @Email: lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net Since 2012
*
-->
<template>
<div class="login-container">
<div class="box-item desc">
<div class="welcome">
<p>欢迎登录 SmartAdmin V3</p>
<p class="sub-welcome">高质量代码的快速开发平台</p>
</div>
<img class="welcome-img" :src="leftBg2" />
</div>
<div class="box-item login">
<img class="login-qr" :src="loginQR" />
<div class="login-title">账号登录</div>
<a-form ref="formRef" class="login-form" :model="loginForm" :rules="rules">
<a-form-item name="loginName">
<a-input v-model:value.trim="loginForm.loginName" placeholder="请输入用户名" />
</a-form-item>
<a-form-item name="password">
<a-input-password
v-model:value="loginForm.password"
autocomplete="on"
:type="showPassword ? 'text' : 'password'"
placeholder="请输入密码:至少三种字符,最小 8 位"
/>
</a-form-item>
<a-form-item name="captchaCode">
<a-input class="captcha-input" v-model:value.trim="loginForm.captchaCode" placeholder="请输入验证码" />
<img class="captcha-img" :src="captchaBase64Image" @click="getCaptcha" />
</a-form-item>
<a-form-item>
<a-checkbox v-model:checked="rememberPwd">记住密码</a-checkbox>
<span> ( 账号admin, 密码123456)</span>
</a-form-item>
<a-form-item>
<div class="btn" @click="onLogin">登录</div>
</a-form-item>
</a-form>
<div class="more">
<div class="title-box">
<p class="line"></p>
<p class="title">其他方式登录</p>
<p class="line"></p>
</div>
<div class="login-type">
<img :src="wechatIcon" />
<img :src="aliIcon" />
<img :src="douyinIcon" />
<img :src="qqIcon" />
<img :src="weiboIcon" />
<img :src="feishuIcon" />
<img :src="googleIcon" />
</div>
</div>
</div>
</div>
</template>
<script setup>
import { message } from 'ant-design-vue';
import { onMounted, onUnmounted, reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
import { loginApi } from '/@/api/system/login-api';
import { SmartLoading } from '/@/components/framework/smart-loading';
import { LOGIN_DEVICE_ENUM } from '/@/constants/system/login-device-const';
import { useUserStore } from '/@/store/modules/system/user';
import loginQR from '/@/assets/images/login/login-qr.png';
import leftBg2 from '/@/assets/images/login/left-bg2.png';
import wechatIcon from '/@/assets/images/login/wechat-icon.png';
import aliIcon from '/@/assets/images/login/ali-icon.png';
import douyinIcon from '/@/assets/images/login/douyin-icon.png';
import qqIcon from '/@/assets/images/login/qq-icon.png';
import weiboIcon from '/@/assets/images/login/weibo-icon.png';
import feishuIcon from '/@/assets/images/login/feishu-icon.png';
import googleIcon from '/@/assets/images/login/google-icon.png';
import { buildRoutes } from '/@/router/index';
import { smartSentry } from '/@/lib/smart-sentry';
import { encryptData } from '/@/lib/encrypt';
import { localSave } from '/@/utils/local-util.js';
import LocalStorageKeyConst from '/@/constants/local-storage-key-const.js';
//--------------------- 登录表单 ---------------------------------
const loginForm = reactive({
loginName: 'admin',
password: '',
captchaCode: '',
captchaUuid: '',
loginDevice: LOGIN_DEVICE_ENUM.PC.value,
});
const rules = {
loginName: [{ required: true, message: '用户名不能为空' }],
password: [{ required: true, message: '密码不能为空' }],
captchaCode: [{ required: true, message: '验证码不能为空' }],
};
const showPassword = ref(false);
const router = useRouter();
const formRef = ref();
const rememberPwd = ref(false);
onMounted(() => {
document.onkeyup = (e) => {
if (e.keyCode == 13) {
onLogin();
}
};
});
onUnmounted(() => {
document.onkeyup = null;
});
//登录
async function onLogin() {
formRef.value.validate().then(async () => {
try {
SmartLoading.show();
// 密码加密
let encryptPasswordForm = Object.assign({}, loginForm, {
password: encryptData(loginForm.password),
});
const res = await loginApi.login(encryptPasswordForm);
stopRefrestCaptchaInterval();
localSave(LocalStorageKeyConst.USER_TOKEN, res.data.token ? res.data.token : '');
message.success('登录成功');
//更新用户信息到pinia
useUserStore().setUserLoginInfo(res.data);
//构建系统的路由
buildRoutes();
router.push('/home');
} catch (e) {
if (e.data && e.data.code !== 0) {
loginForm.captchaCode = '';
getCaptcha();
}
smartSentry.captureError(e);
} finally {
SmartLoading.hide();
}
});
}
//--------------------- 验证码 ---------------------------------
const captchaBase64Image = ref('');
async function getCaptcha() {
try {
let captchaResult = await loginApi.getCaptcha();
captchaBase64Image.value = captchaResult.data.captchaBase64Image;
loginForm.captchaUuid = captchaResult.data.captchaUuid;
beginRefrestCaptchaInterval(captchaResult.data.expireSeconds);
} catch (e) {
console.log(e);
}
}
let refrestCaptchaInterval = null;
function beginRefrestCaptchaInterval(expireSeconds) {
if (refrestCaptchaInterval === null) {
refrestCaptchaInterval = setInterval(getCaptcha, (expireSeconds - 5) * 1000);
}
}
function stopRefrestCaptchaInterval() {
if (refrestCaptchaInterval != null) {
clearInterval(refrestCaptchaInterval);
refrestCaptchaInterval = null;
}
}
onMounted(getCaptcha);
</script>
<style lang="less" scoped>
@import './login.less';
</style>

View File

@@ -0,0 +1,3 @@
NODE_ENV=development
VITE_APP_TITLE='SmartAdmin 开发环境(Dev)'
VITE_APP_API_URL='http://127.0.0.1:1024'

3
smart-app/.env.localhost Normal file
View File

@@ -0,0 +1,3 @@
NODE_ENV=development
VITE_APP_TITLE='SmartH5 本地环境(Local)'
VITE_APP_API_URL='http://127.0.0.1:1024'

3
smart-app/.env.pre Normal file
View File

@@ -0,0 +1,3 @@
NODE_ENV=production
VITE_APP_TITLE='SmartH5 预发布环境(Pre)'
VITE_APP_API_URL='https://app.smartadmin.vip/smart-app-api'

View File

@@ -0,0 +1,3 @@
NODE_ENV=production
VITE_APP_TITLE='Smart App V3.X'
VITE_APP_API_URL='https://app.smartadmin.vip/smart-app-api'

3
smart-app/.env.test Normal file
View File

@@ -0,0 +1,3 @@
NODE_ENV=production
VITE_APP_TITLE='SmartH5 测试环境(Test)'
VITE_APP_API_URL='http://127.0.0.1:1024'

18
smart-app/.eslintignore Normal file
View File

@@ -0,0 +1,18 @@
*.sh
node_modules
lib
*.md
*.woff
*.ttf
.vscode
.idea
dist
public
/docs
.husky
.local
.localhost
/bin
Dockerfile
src/assets

66
smart-app/.eslintrc.cjs Normal file
View File

@@ -0,0 +1,66 @@
/*
* @Description:
* @Author: zhuoda
* @Date: 2021-11-05
* @LastEditTime: 2022-07-05
* @LastEditors: zhuoda
*/
module.exports = {
root: true, //此项是用来告诉eslint找当前配置文件不能往父级查找
env: {
browser: true,
es2021: true,
node: true,
},
parser: 'vue-eslint-parser', //使用vue-eslint-parser 来解析vue文件中的 template和script
parserOptions: {
ecmaVersion: 12, // 默认情况下ESLint使用的是ECMAScript5语法此处我们设置的选项是 es12
sourceType: 'module', // 指定js导入的方式
},
extends: ['plugin:vue/vue3-essential', 'eslint:recommended', 'plugin:vue/base'],
globals: {
defineProps: 'readonly',
defineEmits: 'readonly',
defineExpose: 'readonly',
withDefaults: 'readonly',
},
plugins: ['vue'],
rules: {
'no-unused-vars': [
'error',
// we are only using this rule to check for unused arguments since TS
// catches unused variables but not args.
{ varsIgnorePattern: '.*', args: 'none' },
],
'space-before-function-paren': 'off',
'vue/attributes-order': 'off',
'vue/one-component-per-file': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/attribute-hyphenation': 'off',
'vue/require-default-prop': 'off',
'vue/multi-word-component-names': [
'error',
{
ignores: ['index'], //需要忽略的组件名
},
],
'vue/html-self-closing': [
'error',
{
html: {
void: 'always',
normal: 'never',
component: 'always',
},
svg: 'always',
math: 'always',
},
],
// Enable vue/script-setup-uses-vars rule
'vue/script-setup-uses-vars': 'error',
},
};

21
smart-app/.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
*.local
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

30
smart-app/.prettierrc.cjs Normal file
View File

@@ -0,0 +1,30 @@
/*
* 代码格式化配置
*
* @Author: 1024创新实验室-主任:卓大
* @Date: 2022-09-12 14:44:18
* @Wechat: zhuda1024
* @Email: lab1024@163.com
* @Copyright 1024创新实验室 https://1024lab.net Since 2012
*/
module.exports = {
printWidth: 150, // 每行代码长度默认80
tabWidth: 2, // 缩进空格数
useTabs: false, //不用tab缩进
semi: true, //// 在语句末尾打印分号
singleQuote: true, // 使用单引号而不是双引号
vueIndentScriptAndStyle: true, //Vue文件脚本和样式标签缩进
quoteProps: 'as-needed', // 更改引用对象属性的时间 可选值"<as-needed|consistent|preserve>"
jsxSingleQuote: true, // 在JSX中使用单引号而不是双引号
trailingComma: 'es5', //多行时尽可能打印尾随逗号。(例如,单行数组永远不会出现逗号结尾。) 可选值"<none|es5|all>"默认none
bracketSpacing: true, // 在对象文字中的括号之间打印空格
jsxBracketSameLine: false, //jsx 标签的反尖括号需要换行
arrowParens: 'always', // 在单独的箭头函数参数周围包括括号 always(x) => x \ avoidx => x
rangeStart: 0, // 这两个选项可用于格式化以给定字符偏移量(分别包括和不包括)开始和结束的代码
rangeEnd: Infinity,
requirePragma: false, // 指定要使用的解析器,不需要写文件开头的 @prettier
insertPragma: false, // 不需要自动在文件开头插入 @prettier
proseWrap: 'preserve', // 使用默认的折行标准 always\never\preserve
htmlWhitespaceSensitivity: 'css', // 指定HTML文件的全局空格敏感度 css\strict\ignore
endOfLine: 'auto', // 因为prettier的规范和eslint的换行规则不同所以这个必须配置。要不然每次打开文件都会有一堆的警告;换行符使用 lf 结尾是 可选值"<auto|lf|crlf|cr
};

20
smart-app/index.html Normal file
View File

@@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

89
smart-app/package.json Normal file
View File

@@ -0,0 +1,89 @@
{
"name": "smart-app",
"version": "3.0.0",
"author": {
"name": "1024创新实验室1024lab",
"email": "lab1024@163.com",
"url": "https://www.1024lab.net"
},
"license": "MIT",
"homepage": "https://smartadmin.1024lab.net",
"scripts": {
"dev:app": "uni -p app",
"dev:app-android": "uni -p app-android",
"dev:app-ios": "uni -p app-ios",
"dev:custom": "uni -p",
"dev:h5": "uni",
"dev:h5:ssr": "uni --ssr",
"dev:mp-alipay": "uni -p mp-alipay",
"dev:mp-baidu": "uni -p mp-baidu",
"dev:mp-jd": "uni -p mp-jd",
"dev:mp-kuaishou": "uni -p mp-kuaishou",
"dev:mp-lark": "uni -p mp-lark",
"dev:mp-qq": "uni -p mp-qq",
"dev:mp-toutiao": "uni -p mp-toutiao",
"dev:mp-weixin": "uni -p mp-weixin",
"dev:mp-xhs": "uni -p mp-xhs",
"dev:quickapp-webview": "uni -p quickapp-webview",
"dev:quickapp-webview-huawei": "uni -p quickapp-webview-huawei",
"dev:quickapp-webview-union": "uni -p quickapp-webview-union",
"build:app": "uni build -p app",
"build:app-android": "uni build -p app-android",
"build:app-ios": "uni build -p app-ios",
"build:custom": "uni build -p",
"build:h5": "uni build",
"build:h5:ssr": "uni build --ssr",
"build:mp-alipay": "uni build -p mp-alipay",
"build:mp-baidu": "uni build -p mp-baidu",
"build:mp-jd": "uni build -p mp-jd",
"build:mp-kuaishou": "uni build -p mp-kuaishou",
"build:mp-lark": "uni build -p mp-lark",
"build:mp-qq": "uni build -p mp-qq",
"build:mp-toutiao": "uni build -p mp-toutiao",
"build:mp-weixin": "uni build -p mp-weixin",
"build:mp-xhs": "uni build -p mp-xhs",
"build:quickapp-webview": "uni build -p quickapp-webview",
"build:quickapp-webview-huawei": "uni build -p quickapp-webview-huawei",
"build:quickapp-webview-union": "uni build -p quickapp-webview-union"
},
"dependencies": {
"@dcloudio/uni-app": "3.0.0-3090920231225001",
"@dcloudio/uni-app-plus": "3.0.0-3090920231225001",
"@dcloudio/uni-components": "3.0.0-3090920231225001",
"@dcloudio/uni-h5": "3.0.0-3090920231225001",
"@dcloudio/uni-mp-alipay": "3.0.0-3090920231225001",
"@dcloudio/uni-mp-baidu": "3.0.0-3090920231225001",
"@dcloudio/uni-mp-jd": "3.0.0-3090920231225001",
"@dcloudio/uni-mp-kuaishou": "3.0.0-3090920231225001",
"@dcloudio/uni-mp-lark": "3.0.0-3090920231225001",
"@dcloudio/uni-mp-qq": "3.0.0-3090920231225001",
"@dcloudio/uni-mp-toutiao": "3.0.0-3090920231225001",
"@dcloudio/uni-mp-weixin": "3.0.0-3090920231225001",
"@dcloudio/uni-mp-xhs": "3.0.0-3090920231225001",
"@dcloudio/uni-quickapp-webview": "3.0.0-3090920231225001",
"@dcloudio/uni-ui": "1.5.0",
"crypto-js": "4.1.1",
"dayjs": "1.11.10",
"lodash": "4.17.21",
"pinia": "2.0.36",
"sm-crypto": "0.3.13",
"vue": "3.2.47",
"vue-i18n": "9.1.9"
},
"devDependencies": {
"@dcloudio/types": "3.3.2",
"@dcloudio/uni-automator": "3.0.0-3090920231225001",
"@dcloudio/uni-cli-shared": "3.0.0-3090920231225001",
"@dcloudio/uni-stacktracey": "3.0.0-3090920231225001",
"@dcloudio/vite-plugin-uni": "3.0.0-3090920231225001",
"@vue/runtime-core": "3.2.45",
"eslint": "8.16.0",
"eslint-config-prettier": "9.0.0",
"eslint-plugin-prettier": "5.0.0",
"eslint-plugin-vue": "9.17.0",
"prettier": "3.0.2",
"sass": "1.69.7",
"sass-loader": "10.1.1",
"vite": "4.0.3"
}
}

10
smart-app/shims-uni.d.ts vendored Normal file
View File

@@ -0,0 +1,10 @@
/// <reference types='@dcloudio/types' />
import 'vue'
declare module '@vue/runtime-core' {
type Hooks = App.AppInstance & Page.PageInstance;
interface ComponentCustomOptions extends Hooks {
}
}

22
smart-app/src/App.vue Normal file
View File

@@ -0,0 +1,22 @@
<script>
import { useUserStore } from '/@/store/modules/system/user';
export default {
onLaunch: function () {
useUserStore().getLoginInfo();
},
onShow: function () {
console.log('App Show');
},
onHide: function () {
console.log('App Hide');
},
};
</script>
<style lang="scss">
@import '@/uni_modules/uni-scss/index.scss';
/* 设置基准字体大小为16px */
body {
font-size: 16px;
}
</style>

Some files were not shown because too many files have changed in this diff Show More