同步前端代码并提交相关修复

This commit is contained in:
wkc
2026-04-15 15:28:50 +08:00
parent 79c5317414
commit 71c5744b3d
198 changed files with 28811 additions and 19433 deletions

View File

@@ -9,7 +9,9 @@ CONSOLE_LOG="$LOG_DIR/backend-console.log"
PID_FILE="$LOG_DIR/backend-java.pid" PID_FILE="$LOG_DIR/backend-java.pid"
TARGET_DIR="$ROOT_DIR/ruoyi-admin/target" TARGET_DIR="$ROOT_DIR/ruoyi-admin/target"
JAR_NAME="ruoyi-admin.jar" JAR_NAME="ruoyi-admin.jar"
SERVER_PORT=8080 JAVA_HOME_1_8="/Library/Java/JavaVirtualMachines/jdk1.8.0_202.jdk/Contents/Home"
JAVA_BIN="$JAVA_HOME_1_8/bin/java"
SERVER_PORT=63310
STOP_WAIT_SECONDS=30 STOP_WAIT_SECONDS=30
APP_MARKER="-Dloan.pricing.backend.root=$ROOT_DIR" APP_MARKER="-Dloan.pricing.backend.root=$ROOT_DIR"
JAVA_OPTS="$APP_MARKER -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError" JAVA_OPTS="$APP_MARKER -Duser.timezone=Asia/Shanghai -Xms512m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError"
@@ -42,6 +44,18 @@ ensure_command() {
fi fi
} }
setup_java_env() {
if [ ! -x "$JAVA_BIN" ]; then
log_error "未找到 JDK 1.8: $JAVA_BIN"
exit 1
fi
JAVA_HOME="$JAVA_HOME_1_8"
export JAVA_HOME
PATH="$JAVA_HOME/bin:$PATH"
export PATH
}
is_managed_backend_pid() { is_managed_backend_pid() {
pid="$1" pid="$1"
if [ -z "${pid:-}" ] || ! kill -0 "$pid" 2>/dev/null; then if [ -z "${pid:-}" ] || ! kill -0 "$pid" 2>/dev/null; then
@@ -175,7 +189,7 @@ start_backend() {
( (
cd "$TARGET_DIR" cd "$TARGET_DIR"
nohup java $JAVA_OPTS -jar "$JAR_NAME" >> "$CONSOLE_LOG" 2>&1 & nohup "$JAVA_BIN" $JAVA_OPTS -jar "$JAR_NAME" >> "$CONSOLE_LOG" 2>&1 &
echo $! > "$PID_FILE" echo $! > "$PID_FILE"
) )
@@ -232,8 +246,8 @@ restart_action() {
} }
main() { main() {
setup_java_env
ensure_command mvn ensure_command mvn
ensure_command java
ensure_command lsof ensure_command lsof
ensure_command ps ensure_command ps
ensure_command tail ensure_command tail

View File

@@ -0,0 +1,17 @@
# AGENTS 中文化实施记录
## 修改内容
- 将根目录 `AGENTS.md` 中英文版仓库协作指南完整转换为中文表述。
- 保留原有章节结构与约束语义,仅调整为中文描述,未改变规则内容。
## 影响范围
- 影响文件:`/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/AGENTS.md`
- 本次实施记录:`/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/doc/2026-04-15-AGENTS中文化实施记录.md`
## 说明
- 本次修改仅涉及文档文本,不涉及前端、后端逻辑或配置变更。
- 未执行构建与测试,原因是本次仅为文档中文化调整。
## 保存路径确认
- `AGENTS.md` 保存路径正确:`/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/AGENTS.md`
- 实施记录保存路径正确:`/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/doc/2026-04-15-AGENTS中文化实施记录.md`

View File

@@ -0,0 +1,19 @@
# 后端启动脚本固定 JDK 1.8 实施文档
## 修改内容
-`bin/restart_java_backend.sh` 中新增固定的 JDK 1.8 路径 `/Library/Java/JavaVirtualMachines/jdk1.8.0_202.jdk/Contents/Home`
- 脚本启动时统一设置 `JAVA_HOME``PATH`,确保脚本内执行的 `mvn``java -jar` 都使用 JDK 1.8。
- 启动 Java 进程时改为显式调用固定路径下的 `bin/java`,避免落回系统默认的 JDK 21。
## 适配依据
- 当前机器默认 `JAVA_HOME` 为 JDK 21不满足本项目需要固定 JDK 1.8 的要求。
- 当前机器已安装可用的 JDK 1.8`/Library/Java/JavaVirtualMachines/jdk1.8.0_202.jdk/Contents/Home`
- 本次按“写死当前机器 JDK 1.8 路径”的方案实现,不引入额外的自动探测逻辑。
## 验证方式
- 执行 `sh -n bin/restart_java_backend.sh` 校验脚本语法。
- 执行 `/Library/Java/JavaVirtualMachines/jdk1.8.0_202.jdk/Contents/Home/bin/java -version` 校验固定路径下的 Java 为 1.8。
## 保存路径确认
- 脚本路径:`/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/bin/restart_java_backend.sh`
- 本次实施文档路径:`/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/doc/2026-04-15-后端启动脚本固定JDK18实施文档.md`

View File

@@ -0,0 +1,25 @@
# 后端端口改为 `63310` 实施记录
## 修改时间
- 2026-04-15
## 修改内容
- 将重启脚本 [bin/restart_java_backend.sh](/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/bin/restart_java_backend.sh) 中的 `SERVER_PORT``8080` 调整为 `63310`
- 核对后端环境配置,确认 [application-dev.yml](/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/ruoyi-admin/src/main/resources/application-dev.yml)、[application-pro.yml](/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/ruoyi-admin/src/main/resources/application-pro.yml)、[application-uat.yml](/Users/wkc/Desktop/loan-pricing/loan-pricing-jdk-1.8/ruoyi-admin/src/main/resources/application-uat.yml) 已经使用 `63310`
## 影响范围
- 后端重启脚本的端口监听检测
- 后端脚本状态查询结果
## 验证结果
- 通过源码检索确认后端主环境配置和脚本统一指向 `63310`
- 通过 `bash -n bin/restart_java_backend.sh` 确认脚本语法正常
## 说明
- 当前开发、生产、UAT 环境配置原本已经是 `63310`
- 本次主要修正的是脚本里仍残留的 `8080`

View File

@@ -0,0 +1,16 @@
# 2026-04-15 直接复制源分支前端代码实施记录
## 修改内容
- 按用户要求,直接将 `origin/892-without-redis``ruoyi-ui` 整体复制到当前分支。
- 覆盖了源分支已有的前端页面、路由、API、布局、样式、构建配置与依赖锁文件。
- 同步删除了当前分支中源分支不存在的前端文件,确保前端代码基线与源分支保持一致。
## 涉及范围
- `ruoyi-ui/`
## 验证结果
- 使用 `nvm` 切换 Node 版本后执行 `npm install`
- 执行 `npm run build:prod`

View File

@@ -0,0 +1,16 @@
# 2026-04-15 贷款定价密钥统一为密码传输配置后端实施记录
## 修改内容
- 将贷款定价敏感字段加解密服务统一为只读取 `security.password-transfer.key`
- 删除对 `loan-pricing.sensitive.key` 的依赖,避免出现双配置源。
- 调整定向单元测试,校验未配置时抛出的错误信息改为 `security.password-transfer.key 未配置`
## 涉及文件
- `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/SensitiveFieldCryptoService.java`
- `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/SensitiveFieldCryptoServiceTest.java`
## 验证结果
- 执行 `mvn -pl ruoyi-admin -am -DskipTests package` 验证后端整体打包。

View File

@@ -0,0 +1,14 @@
# 2026-04-15 贷款定价敏感字段密钥配置修复后端实施记录
## 修改内容
- 修复贷款定价首页列表请求返回 `loan-pricing.sensitive.key 未配置` 的问题。
-`application.yml` 中新增 `loan-pricing.sensitive.key`,并直接复用现有的 `security.password-transfer.key`,保证贷款定价敏感字段加解密与密码传输使用同一把密钥。
## 涉及文件
- `ruoyi-admin/src/main/resources/application.yml`
## 验证结果
- 执行 `mvn -pl ruoyi-admin -am -DskipTests package`,验证后端配置修改后可正常完成打包。

View File

@@ -0,0 +1,14 @@
# 2026-04-15 首页改为利率测算列表前端实施记录
## 修改内容
- 将前端默认首页路由从 `@/views/index` 调整为 `@/views/loanPricing/workflow/index`,使登录后的首页直接进入利率测算列表。
- 为利率测算详情页补充显式命名路由 `LoanPricingWorkflowDetail`,保证首页列表点击“查看”后可以正常进入详情页。
## 涉及文件
- `ruoyi-ui/src/router/index.js`
## 验证结果
- 使用 `nvm` 切换 Node 版本后执行 `npm run build:prod`,验证前端路由配置可以正常通过生产构建。

View File

@@ -0,0 +1,18 @@
# 2026-04-15 首页面包屑与贷款定价密钥异常修复实施记录
## 修改内容
- 修复首页访问 `/index` 时面包屑重复注入首页节点,导致前端出现 `Duplicate keys detected: '/index'` 警告的问题。
- 修复贷款定价敏感字段加解密服务只读取 `loan-pricing.sensitive.key`,未在专用配置缺失时回退到 `security.password-transfer.key`,导致首页列表接口返回 `loan-pricing.sensitive.key 未配置` 的问题。
## 涉及文件
- `ruoyi-ui/src/components/Breadcrumb/index.vue`
- `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/SensitiveFieldCryptoService.java`
- `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/SensitiveFieldCryptoServiceTest.java`
- `ruoyi-ui/tests/home-breadcrumb-dedup.test.js`
## 验证结果
- 已执行 `node ruoyi-ui/tests/home-breadcrumb-dedup.test.js`,结果通过。
- 已尝试执行 `mvn -pl ruoyi-loan-pricing -am -Dtest=SensitiveFieldCryptoServiceTest -Dsurefire.failIfNoSpecifiedTests=false test`,但当前 `ruoyi-loan-pricing` 模块存在与本次改动无关的编译问题,导致无法完成该条 Maven 定向验证。

View File

@@ -35,7 +35,7 @@
<logback.version>1.2.13</logback.version> <logback.version>1.2.13</logback.version>
<spring-security.version>5.7.14</spring-security.version> <spring-security.version>5.7.14</spring-security.version>
<spring-framework.version>5.3.39</spring-framework.version> <spring-framework.version>5.3.39</spring-framework.version>
<mybatis-plus.version>3.5.7</mybatis-plus.version> <mybatis-plus.version>3.5.1</mybatis-plus.version>
</properties> </properties>
<!-- 依赖声明 --> <!-- 依赖声明 -->

View File

@@ -1,3 +1,22 @@
# 开发环境配置
server:
# 服务器的HTTP端口默认为63310
port: 63310
servlet:
# 应用的访问路径
context-path: /
tomcat:
# tomcat的URI编码
uri-encoding: UTF-8
# 连接数满后的排队数默认为100
accept-count: 1000
threads:
# tomcat最大线程数默认为200
max: 800
# Tomcat启动初始化的线程数默认值10
min-spare: 100
# 数据源配置 # 数据源配置
spring: spring:
datasource: datasource:
@@ -58,4 +77,7 @@ spring:
merge-sql: true merge-sql: true
wall: wall:
config: config:
multi-statement-allow: true multi-statement-allow: true
model:
url: http://localhost:63310/rate/pricing/mock/invokeModel

View File

@@ -81,6 +81,4 @@ spring:
model: model:
url: http://64.202.32.40:8083/api/service/interface/invokeService/syllcs url: http://64.202.32.40:8083/api/service/interface/invokeService/syllcs
security:
password-transfer:
key: "1234567890abcdef"

View File

@@ -81,6 +81,4 @@ spring:
model: model:
url: http://localhost:63310/rate/pricing/mock/invokeModel url: http://localhost:63310/rate/pricing/mock/invokeModel
security:
password-transfer:
key: "1234567890abcdef"

View File

@@ -13,23 +13,6 @@ ruoyi:
# 验证码类型 math 数字计算 char 字符验证 # 验证码类型 math 数字计算 char 字符验证
captchaType: math captchaType: math
# 开发环境配置
server:
# 服务器的HTTP端口默认为8080
port: 8080
servlet:
# 应用的访问路径
context-path: /
tomcat:
# tomcat的URI编码
uri-encoding: UTF-8
# 连接数满后的排队数默认为100
accept-count: 1000
threads:
# tomcat最大线程数默认为200
max: 800
# Tomcat启动初始化的线程数默认值10
min-spare: 100
# 日志配置 # 日志配置
logging: logging:
@@ -60,14 +43,14 @@ spring:
max-file-size: 10MB max-file-size: 10MB
# 设置总上传的文件大小 # 设置总上传的文件大小
max-request-size: 20MB max-request-size: 20MB
# 服务模块 # 服务模块
devtools: devtools:
restart: restart:
# 热部署开关 # 热部署开关
enabled: true enabled: true
# token配置 # token配置
token: token:
# 令牌自定义标识 # 令牌自定义标识
header: Authorization header: Authorization
# 令牌密钥 # 令牌密钥
@@ -75,8 +58,8 @@ token:
# 令牌有效期默认30分钟 # 令牌有效期默认30分钟
expireTime: 30 expireTime: 30
# MyBatis配置 # MyBatis-Plus配置
mybatis: mybatis-plus:
# 搜索指定包别名 # 搜索指定包别名
typeAliasesPackage: com.ruoyi.**.domain typeAliasesPackage: com.ruoyi.**.domain
# 配置mapper的扫描找到所有的mapper.xml映射文件 # 配置mapper的扫描找到所有的mapper.xml映射文件
@@ -98,21 +81,21 @@ swagger:
pathMapping: /dev-api pathMapping: /dev-api
# 防盗链配置 # 防盗链配置
referer: referer:
# 防盗链开关 # 防盗链开关
enabled: false enabled: false
# 允许的域名列表 # 允许的域名列表
allowed-domains: localhost,127.0.0.1,ruoyi.vip,www.ruoyi.vip allowed-domains: localhost,127.0.0.1,ruoyi.vip,www.ruoyi.vip
security: # 防止XSS攻击
password-transfer: xss:
key: "1234567890abcdef"
# 防止XSS攻击
xss:
# 过滤开关 # 过滤开关
enabled: true enabled: true
# 排除链接(多个用逗号分隔) # 排除链接(多个用逗号分隔)
excludes: /system/notice excludes: /system/notice
# 匹配链接 # 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/* urlPatterns: /system/*,/monitor/*,/tool/*
security:
password-transfer:
key: "1234567890abcdef"

View File

@@ -35,11 +35,16 @@
<artifactId>spring-boot-starter-security</artifactId> <artifactId>spring-boot-starter-security</artifactId>
</dependency> </dependency>
<!-- pagehelper 分页插件 --> <!-- pagehelper 分页插件 -->
<dependency> <dependency>
<groupId>com.github.pagehelper</groupId> <groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId> <artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- 自定义验证注解 --> <!-- 自定义验证注解 -->
<dependency> <dependency>

View File

@@ -39,15 +39,20 @@ public class DictUtils
* @param key 参数键 * @param key 参数键
* @return dictDatas 字典数据列表 * @return dictDatas 字典数据列表
*/ */
public static List<SysDictData> getDictCache(String key) public static List<SysDictData> getDictCache(String key)
{ {
JSONArray arrayCache = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key)); Object cacheObject = SpringUtils.getBean(RedisCache.class).getCacheObject(getCacheKey(key));
if (StringUtils.isNotNull(arrayCache)) if (cacheObject instanceof List)
{ {
return arrayCache.toList(SysDictData.class); return (List<SysDictData>) cacheObject;
} }
return null; if (cacheObject instanceof JSONArray)
} {
JSONArray arrayCache = (JSONArray) cacheObject;
return arrayCache.toList(SysDictData.class);
}
return null;
}
/** /**
* 根据字典类型和字典值获取字典标签 * 根据字典类型和字典值获取字典标签

View File

@@ -0,0 +1,46 @@
package com.ruoyi.common.utils;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.util.Collections;
import java.util.List;
import com.ruoyi.common.core.cache.InMemoryCacheStore;
import com.ruoyi.common.core.domain.entity.SysDictData;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.utils.spring.SpringUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.test.util.ReflectionTestUtils;
class DictUtilsTest
{
@AfterEach
void tearDown()
{
ReflectionTestUtils.setField(SpringUtils.class, "beanFactory", null);
}
@Test
void shouldReturnDictListWhenCacheStoresArrayList()
{
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
beanFactory.registerSingleton("redisCache", new RedisCache(new InMemoryCacheStore()));
ReflectionTestUtils.setField(SpringUtils.class, "beanFactory", beanFactory);
SysDictData dictData = new SysDictData();
dictData.setDictType("sys_normal_disable");
dictData.setDictLabel("正常");
dictData.setDictValue("0");
DictUtils.setDictCache("sys_normal_disable", Collections.singletonList(dictData));
List<SysDictData> dictCache = DictUtils.getDictCache("sys_normal_disable");
assertNotNull(dictCache);
assertEquals(1, dictCache.size());
assertEquals("正常", dictCache.get(0).getDictLabel());
assertEquals("0", dictCache.get(0).getDictValue());
}
}

View File

@@ -35,15 +35,10 @@
<artifactId>druid-spring-boot-starter</artifactId> <artifactId>druid-spring-boot-starter</artifactId>
</dependency> </dependency>
<!-- 验证码 -->
<dependency> <dependency>
<groupId>com.baomidou</groupId> <groupId>pro.fessional</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId> <artifactId>kaptcha</artifactId>
</dependency>
<!-- 验证码 -->
<dependency>
<groupId>pro.fessional</groupId>
<artifactId>kaptcha</artifactId>
<exclusions> <exclusions>
<exclusion> <exclusion>
<artifactId>servlet-api</artifactId> <artifactId>servlet-api</artifactId>

View File

@@ -1,9 +1,6 @@
package com.ruoyi.framework.config; package com.ruoyi.framework.config;
import java.util.TimeZone; import java.util.TimeZone;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan; import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
@@ -30,12 +27,4 @@ public class ApplicationConfig
{ {
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault()); return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(TimeZone.getDefault());
} }
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor()
{
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
} }

View File

@@ -1,140 +0,0 @@
package com.ruoyi.framework.config;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import javax.sql.DataSource;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.io.VFS;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.boot.autoconfigure.SpringBootVFS;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.util.ClassUtils;
import com.ruoyi.common.utils.StringUtils;
/**
* Mybatis支持*匹配扫描包
*
* @author ruoyi
*/
@Configuration
public class MyBatisConfig
{
@Autowired
private Environment env;
@Autowired(required = false)
private Interceptor[] interceptors;
static final String DEFAULT_RESOURCE_PATTERN = "**/*.class";
public static String setTypeAliasesPackage(String typeAliasesPackage)
{
ResourcePatternResolver resolver = (ResourcePatternResolver) new PathMatchingResourcePatternResolver();
MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(resolver);
List<String> allResult = new ArrayList<String>();
try
{
for (String aliasesPackage : typeAliasesPackage.split(","))
{
List<String> result = new ArrayList<String>();
aliasesPackage = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX
+ ClassUtils.convertClassNameToResourcePath(aliasesPackage.trim()) + "/" + DEFAULT_RESOURCE_PATTERN;
Resource[] resources = resolver.getResources(aliasesPackage);
if (resources != null && resources.length > 0)
{
MetadataReader metadataReader = null;
for (Resource resource : resources)
{
if (resource.isReadable())
{
metadataReader = metadataReaderFactory.getMetadataReader(resource);
try
{
result.add(Class.forName(metadataReader.getClassMetadata().getClassName()).getPackage().getName());
}
catch (ClassNotFoundException e)
{
e.printStackTrace();
}
}
}
}
if (result.size() > 0)
{
HashSet<String> hashResult = new HashSet<String>(result);
allResult.addAll(hashResult);
}
}
if (allResult.size() > 0)
{
typeAliasesPackage = String.join(",", (String[]) allResult.toArray(new String[0]));
}
else
{
throw new RuntimeException("mybatis typeAliasesPackage 路径扫描错误,参数typeAliasesPackage:" + typeAliasesPackage + "未找到任何包");
}
}
catch (IOException e)
{
e.printStackTrace();
}
return typeAliasesPackage;
}
public Resource[] resolveMapperLocations(String[] mapperLocations)
{
ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
List<Resource> resources = new ArrayList<Resource>();
if (mapperLocations != null)
{
for (String mapperLocation : mapperLocations)
{
try
{
Resource[] mappers = resourceResolver.getResources(mapperLocation);
resources.addAll(Arrays.asList(mappers));
}
catch (IOException e)
{
// ignore
}
}
}
return resources.toArray(new Resource[resources.size()]);
}
@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception
{
String typeAliasesPackage = env.getProperty("mybatis.typeAliasesPackage");
String mapperLocations = env.getProperty("mybatis.mapperLocations");
String configLocation = env.getProperty("mybatis.configLocation");
typeAliasesPackage = setTypeAliasesPackage(typeAliasesPackage);
VFS.addImplClass(SpringBootVFS.class);
final MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
sessionFactory.setTypeAliasesPackage(typeAliasesPackage);
sessionFactory.setMapperLocations(resolveMapperLocations(StringUtils.split(mapperLocations, ",")));
sessionFactory.setConfigLocation(new DefaultResourceLoader().getResource(configLocation));
if (interceptors != null)
{
sessionFactory.setPlugins(interceptors);
}
return sessionFactory.getObject();
}
}

View File

@@ -0,0 +1,19 @@
package com.ruoyi.framework.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig
{
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor()
{
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}

View File

@@ -14,13 +14,13 @@ public class LoanPricingSensitiveDisplayService
} }
if (custName.contains("公司") && custName.length() > 4) if (custName.contains("公司") && custName.length() > 4)
{ {
return custName.substring(0, 2) + "*".repeat(custName.length() - 4) + custName.substring(custName.length() - 2); return custName.substring(0, 2) + repeatMask(custName.length() - 4) + custName.substring(custName.length() - 2);
} }
if (custName.length() == 1) if (custName.length() == 1)
{ {
return custName; return custName;
} }
return custName.substring(0, 1) + "*".repeat(custName.length() - 1); return custName.substring(0, 1) + repeatMask(custName.length() - 1);
} }
public String maskIdNum(String idNum) public String maskIdNum(String idNum)
@@ -31,16 +31,30 @@ public class LoanPricingSensitiveDisplayService
} }
if (idNum.startsWith("91") && idNum.length() == 18) if (idNum.startsWith("91") && idNum.length() == 18)
{ {
return idNum.substring(0, 2) + "*".repeat(13) + idNum.substring(idNum.length() - 3); return idNum.substring(0, 2) + repeatMask(13) + idNum.substring(idNum.length() - 3);
} }
if (idNum.matches("\\d{17}[\\dXx]")) if (idNum.matches("\\d{17}[\\dXx]"))
{ {
return idNum.substring(0, 4) + "*".repeat(8) + idNum.substring(idNum.length() - 4); return idNum.substring(0, 4) + repeatMask(8) + idNum.substring(idNum.length() - 4);
} }
if (idNum.length() > 5) if (idNum.length() > 5)
{ {
return idNum.substring(0, 2) + "*".repeat(idNum.length() - 5) + idNum.substring(idNum.length() - 3); return idNum.substring(0, 2) + repeatMask(idNum.length() - 5) + idNum.substring(idNum.length() - 3);
} }
return "*".repeat(idNum.length()); return repeatMask(idNum.length());
}
private String repeatMask(int count)
{
if (count <= 0)
{
return "";
}
StringBuilder builder = new StringBuilder(count);
for (int i = 0; i < count; i++)
{
builder.append('*');
}
return builder.toString();
} }
} }

View File

@@ -15,7 +15,7 @@ public class SensitiveFieldCryptoService
{ {
private final String key; private final String key;
public SensitiveFieldCryptoService(@Value("${loan-pricing.sensitive.key:}") String key) public SensitiveFieldCryptoService(@Value("${security.password-transfer.key:}") String key)
{ {
this.key = key; this.key = key;
} }
@@ -62,7 +62,7 @@ public class SensitiveFieldCryptoService
{ {
if (!StringUtils.hasText(key)) if (!StringUtils.hasText(key))
{ {
throw new IllegalStateException("loan-pricing.sensitive.key 未配置"); throw new IllegalStateException("security.password-transfer.key 未配置");
} }
} }
} }

View File

@@ -27,6 +27,7 @@ class SensitiveFieldCryptoServiceTest
{ {
SensitiveFieldCryptoService service = new SensitiveFieldCryptoService(""); SensitiveFieldCryptoService service = new SensitiveFieldCryptoService("");
assertThrows(IllegalStateException.class, () -> service.encrypt("张三")); IllegalStateException exception = assertThrows(IllegalStateException.class, () -> service.encrypt("张三"));
assertEquals("security.password-transfer.key 未配置", exception.getMessage());
} }
} }

View File

@@ -84,7 +84,7 @@ class LoanPricingWorkflowServiceImplTest
row.setCalculateRate("6.15"); row.setCalculateRate("6.15");
Page<LoanPricingWorkflowListVO> pageResult = new Page<>(1, 10); Page<LoanPricingWorkflowListVO> pageResult = new Page<>(1, 10);
pageResult.setRecords(java.util.List.of(row)); pageResult.setRecords(Collections.singletonList(row));
when(loanPricingWorkflowMapper.selectWorkflowPageWithRates(any(), any())).thenReturn(pageResult); when(loanPricingWorkflowMapper.selectWorkflowPageWithRates(any(), any())).thenReturn(pageResult);

View File

@@ -1,22 +1,22 @@
# 告诉EditorConfig插件这是根文件不用继续往上查找 # 告诉EditorConfig插件这是根文件不用继续往上查找
root = true root = true
# 匹配全部文件 # 匹配全部文件
[*] [*]
# 设置字符集 # 设置字符集
charset = utf-8 charset = utf-8
# 缩进风格可选space、tab # 缩进风格可选space、tab
indent_style = space indent_style = space
# 缩进的空格数 # 缩进的空格数
indent_size = 2 indent_size = 2
# 结尾换行符可选lf、cr、crlf # 结尾换行符可选lf、cr、crlf
end_of_line = lf end_of_line = lf
# 在文件结尾插入新行 # 在文件结尾插入新行
insert_final_newline = true insert_final_newline = true
# 删除一行中的前后空格 # 删除一行中的前后空格
trim_trailing_whitespace = true trim_trailing_whitespace = true
# 匹配md结尾的文件 # 匹配md结尾的文件
[*.md] [*.md]
insert_final_newline = false insert_final_newline = false
trim_trailing_whitespace = false trim_trailing_whitespace = false

View File

@@ -1,9 +1,9 @@
# 页面标题 # 页面标题
VUE_APP_TITLE = 若依管理系统 VUE_APP_TITLE = 上虞利率定价系统
# 开发环境配置 # 开发环境配置
ENV = 'development' ENV = 'development'
# 若依管理系统/开发环境 # 若依管理系统/开发环境
VUE_APP_BASE_API = '/dev-api' VUE_APP_BASE_API = '/dev-api'
VUE_APP_PASSWORD_TRANSFER_KEY = '1234567890abcdef' VUE_APP_PASSWORD_TRANSFER_KEY = '1234567890abcdef'

View File

@@ -1,9 +1,9 @@
# 页面标题 # 页面标题
VUE_APP_TITLE = 若依管理系统 VUE_APP_TITLE = 上虞利率定价系统
# 生产环境配置 # 生产环境配置
ENV = 'production' ENV = 'production'
# 若依管理系统/生产环境 # 若依管理系统/生产环境
VUE_APP_BASE_API = '/prod-api' VUE_APP_BASE_API = '/prod-api'
VUE_APP_PASSWORD_TRANSFER_KEY = '1234567890abcdef' VUE_APP_PASSWORD_TRANSFER_KEY = '1234567890abcdef'

View File

@@ -1,12 +1,13 @@
# 页面标题 # 页面标题
VUE_APP_TITLE = 若依管理系统 VUE_APP_TITLE = 上虞利率定价系统
BABEL_ENV = production BABEL_ENV = production
NODE_ENV = production NODE_ENV = production
# 测试环境配置 # 测试环境配置
ENV = 'staging' ENV = 'staging'
# 若依管理系统/测试环境 # 若依管理系统/测试环境
VUE_APP_BASE_API = '/stage-api' VUE_APP_BASE_API = '/stage-api'
VUE_APP_PASSWORD_TRANSFER_KEY = '1234567890abcdef'

46
ruoyi-ui/.gitignore vendored
View File

@@ -1,23 +1,23 @@
.DS_Store .DS_Store
node_modules/ node_modules/
dist/ dist/
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
**/*.log **/*.log
tests/**/coverage/ tests/**/coverage/
tests/e2e/reports tests/e2e/reports
selenium-debug.log selenium-debug.log
# Editor directories and files # Editor directories and files
.idea .idea
.vscode .vscode
*.suo *.suo
*.ntvs* *.ntvs*
*.njsproj *.njsproj
*.sln *.sln
*.local *.local
package-lock.json package-lock.json
yarn.lock yarn.lock

View File

@@ -1,30 +1,30 @@
## 开发 ## 开发
```bash ```bash
# 克隆项目 # 克隆项目
git clone https://gitee.com/y_project/RuoYi-Vue git clone https://gitee.com/y_project/RuoYi-Vue
# 进入项目目录 # 进入项目目录
cd ruoyi-ui cd ruoyi-ui
# 安装依赖 # 安装依赖
npm install npm install
# 建议不要直接使用 cnpm 安装依赖,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题 # 建议不要直接使用 cnpm 安装依赖,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题
npm install --registry=https://registry.npmmirror.com npm install --registry=https://registry.npmmirror.com
# 启动服务 # 启动服务
npm run dev npm run dev
``` ```
浏览器访问 http://localhost:80 浏览器访问 http://localhost:80
## 发布 ## 发布
```bash ```bash
# 构建测试环境 # 构建测试环境
npm run build:stage npm run build:stage
# 构建生产环境 # 构建生产环境
npm run build:prod npm run build:prod
``` ```

View File

@@ -1,13 +1,13 @@
module.exports = { module.exports = {
presets: [ presets: [
// https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app // https://github.com/vuejs/vue-cli/tree/master/packages/@vue/babel-preset-app
'@vue/cli-plugin-babel/preset' '@vue/cli-plugin-babel/preset'
], ],
'env': { 'env': {
'development': { 'development': {
// babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require(). // babel-plugin-dynamic-import-node plugin only does one thing by converting all import() to require().
// This plugin can significantly increase the speed of hot updates, when you have a large number of pages. // This plugin can significantly increase the speed of hot updates, when you have a large number of pages.
'plugins': ['dynamic-import-node'] 'plugins': ['dynamic-import-node']
} }
} }
} }

View File

@@ -1,12 +1,12 @@
@echo off @echo off
echo. echo.
echo [信息] 安装Web工程生成node_modules文件。 echo [信息] 安装Web工程生成node_modules文件。
echo. echo.
%~d0 %~d0
cd %~dp0 cd %~dp0
cd .. cd ..
npm install --registry=https://registry.npmmirror.com npm install --registry=https://registry.npmmirror.com
pause pause

View File

@@ -1,12 +1,12 @@
@echo off @echo off
echo. echo.
echo [信息] 使用 Vue CLI 命令运行 Web 工程。 echo [信息] 使用 Vue CLI 命令运行 Web 工程。
echo. echo.
%~d0 %~d0
cd %~dp0 cd %~dp0
cd .. cd ..
npm run dev npm run dev
pause pause

View File

@@ -1,35 +1,35 @@
const { run } = require('runjs') const { run } = require('runjs')
const chalk = require('chalk') const chalk = require('chalk')
const config = require('../vue.config.js') const config = require('../vue.config.js')
const rawArgv = process.argv.slice(2) const rawArgv = process.argv.slice(2)
const args = rawArgv.join(' ') const args = rawArgv.join(' ')
if (process.env.npm_config_preview || rawArgv.includes('--preview')) { if (process.env.npm_config_preview || rawArgv.includes('--preview')) {
const report = rawArgv.includes('--report') const report = rawArgv.includes('--report')
run(`vue-cli-service build ${args}`) run(`vue-cli-service build ${args}`)
const port = 9526 const port = 9526
const publicPath = config.publicPath const publicPath = config.publicPath
var connect = require('connect') var connect = require('connect')
var serveStatic = require('serve-static') var serveStatic = require('serve-static')
const app = connect() const app = connect()
app.use( app.use(
publicPath, publicPath,
serveStatic('./dist', { serveStatic('./dist', {
index: ['index.html', '/'] index: ['index.html', '/']
}) })
) )
app.listen(port, function () { app.listen(port, function () {
console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`)) console.log(chalk.green(`> Preview at http://localhost:${port}${publicPath}`))
if (report) { if (report) {
console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`)) console.log(chalk.green(`> Report at http://localhost:${port}${publicPath}report.html`))
} }
}) })
} else { } else {
run(`vue-cli-service build ${args}`) run(`vue-cli-service build ${args}`)
} }

BIN
ruoyi-ui/dist.zip Normal file

Binary file not shown.

14195
ruoyi-ui/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,73 +1,77 @@
{ {
"name": "ruoyi", "name": "ruoyi",
"version": "3.9.2", "version": "3.9.1",
"description": "若依管理系统", "description": "若依管理系统",
"author": "若依", "author": "若依",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"dev": "vue-cli-service serve", "dev": "vue-cli-service serve",
"build:prod": "vue-cli-service build", "build:prod": "vue-cli-service build",
"build:stage": "vue-cli-service build --mode staging", "build:stage": "vue-cli-service build --mode staging",
"preview": "node build/index.js --preview" "preview": "node build/index.js --preview",
}, "test:password-transfer": "node tests/password-transfer-api.test.js",
"keywords": [ "test:retail-display-fields": "node tests/retail-display-fields.test.js",
"vue", "test:personal-create-input-params": "node tests/personal-create-input-params.test.js",
"admin", "test:id-number-validation-removal": "node tests/id-number-validation-removal.test.js"
"dashboard", },
"element-ui", "keywords": [
"boilerplate", "vue",
"admin-template", "admin",
"management-system" "dashboard",
], "element-ui",
"repository": { "boilerplate",
"type": "git", "admin-template",
"url": "https://gitee.com/y_project/RuoYi-Vue.git" "management-system"
}, ],
"dependencies": { "repository": {
"@riophae/vue-treeselect": "0.4.0", "type": "git",
"axios": "0.30.3", "url": "https://gitee.com/y_project/RuoYi-Vue.git"
"clipboard": "2.0.8", },
"core-js": "3.37.1", "dependencies": {
"crypto-js": "4.2.0", "@riophae/vue-treeselect": "0.4.0",
"echarts": "5.4.0", "axios": "0.28.1",
"element-ui": "2.15.14", "clipboard": "2.0.8",
"file-saver": "2.0.5", "core-js": "3.37.1",
"fuse.js": "6.4.3", "echarts": "5.4.0",
"highlight.js": "9.18.5", "element-ui": "2.15.14",
"js-beautify": "1.13.0", "file-saver": "2.0.5",
"js-cookie": "3.0.1", "fuse.js": "6.4.3",
"jsencrypt": "3.0.0-rc.1", "highlight.js": "9.18.5",
"nprogress": "0.2.0", "crypto-js": "4.2.0",
"quill": "2.0.2", "js-beautify": "1.13.0",
"screenfull": "5.0.2", "js-cookie": "3.0.1",
"sortablejs": "1.10.2", "jsencrypt": "3.0.0-rc.1",
"vue": "2.6.12", "nprogress": "0.2.0",
"vue-count-to": "1.0.13", "quill": "2.0.2",
"vue-cropper": "0.5.5", "screenfull": "5.0.2",
"vue-router": "3.4.9", "sortablejs": "1.10.2",
"vuedraggable": "2.24.3", "splitpanes": "2.4.1",
"vuex": "3.6.0" "vue": "2.6.12",
}, "vue-count-to": "1.0.13",
"devDependencies": { "vue-cropper": "0.5.5",
"@vue/cli-plugin-babel": "4.4.6", "vue-router": "3.4.9",
"@vue/cli-service": "4.4.6", "vuedraggable": "2.24.3",
"babel-plugin-dynamic-import-node": "2.3.3", "vuex": "3.6.0"
"chalk": "4.1.0", },
"compression-webpack-plugin": "6.1.2", "devDependencies": {
"connect": "3.6.6", "@vue/cli-plugin-babel": "4.4.6",
"html-webpack-plugin": "3.2.0", "@vue/cli-service": "4.4.6",
"sass": "1.32.13", "babel-plugin-dynamic-import-node": "2.3.3",
"sass-loader": "10.1.1", "chalk": "4.1.0",
"script-ext-html-webpack-plugin": "2.1.5", "compression-webpack-plugin": "6.1.2",
"svg-sprite-loader": "5.1.1", "connect": "3.6.6",
"vue-template-compiler": "2.6.12" "sass": "1.32.13",
}, "sass-loader": "10.1.1",
"engines": { "script-ext-html-webpack-plugin": "2.1.5",
"node": ">=8.9", "svg-sprite-loader": "5.1.1",
"npm": ">= 3.0.0" "vue-template-compiler": "2.6.12"
}, },
"browserslist": [ "engines": {
"> 1%", "node": ">=8.9",
"last 2 versions" "npm": ">= 3.0.0"
] },
} "browserslist": [
"> 1%",
"last 2 versions"
]
}

File diff suppressed because one or more lines are too long

View File

@@ -1,208 +1,208 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit"> <meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= webpackConfig.name %></title> <title><%= webpackConfig.name %></title>
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]--> <!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
<style> <style>
html, html,
body, body,
#app { #app {
height: 100%; height: 100%;
margin: 0px; margin: 0px;
padding: 0px; padding: 0px;
} }
.chromeframe { .chromeframe {
margin: 0.2em 0; margin: 0.2em 0;
background: #ccc; background: #ccc;
color: #000; color: #000;
padding: 0.2em 0; padding: 0.2em 0;
} }
#loader-wrapper { #loader-wrapper {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
z-index: 999999; z-index: 999999;
} }
#loader { #loader {
display: block; display: block;
position: relative; position: relative;
left: 50%; left: 50%;
top: 50%; top: 50%;
width: 150px; width: 150px;
height: 150px; height: 150px;
margin: -75px 0 0 -75px; margin: -75px 0 0 -75px;
border-radius: 50%; border-radius: 50%;
border: 3px solid transparent; border: 3px solid transparent;
border-top-color: #FFF; border-top-color: #FFF;
-webkit-animation: spin 2s linear infinite; -webkit-animation: spin 2s linear infinite;
-ms-animation: spin 2s linear infinite; -ms-animation: spin 2s linear infinite;
-moz-animation: spin 2s linear infinite; -moz-animation: spin 2s linear infinite;
-o-animation: spin 2s linear infinite; -o-animation: spin 2s linear infinite;
animation: spin 2s linear infinite; animation: spin 2s linear infinite;
z-index: 1001; z-index: 1001;
} }
#loader:before { #loader:before {
content: ""; content: "";
position: absolute; position: absolute;
top: 5px; top: 5px;
left: 5px; left: 5px;
right: 5px; right: 5px;
bottom: 5px; bottom: 5px;
border-radius: 50%; border-radius: 50%;
border: 3px solid transparent; border: 3px solid transparent;
border-top-color: #FFF; border-top-color: #FFF;
-webkit-animation: spin 3s linear infinite; -webkit-animation: spin 3s linear infinite;
-moz-animation: spin 3s linear infinite; -moz-animation: spin 3s linear infinite;
-o-animation: spin 3s linear infinite; -o-animation: spin 3s linear infinite;
-ms-animation: spin 3s linear infinite; -ms-animation: spin 3s linear infinite;
animation: spin 3s linear infinite; animation: spin 3s linear infinite;
} }
#loader:after { #loader:after {
content: ""; content: "";
position: absolute; position: absolute;
top: 15px; top: 15px;
left: 15px; left: 15px;
right: 15px; right: 15px;
bottom: 15px; bottom: 15px;
border-radius: 50%; border-radius: 50%;
border: 3px solid transparent; border: 3px solid transparent;
border-top-color: #FFF; border-top-color: #FFF;
-moz-animation: spin 1.5s linear infinite; -moz-animation: spin 1.5s linear infinite;
-o-animation: spin 1.5s linear infinite; -o-animation: spin 1.5s linear infinite;
-ms-animation: spin 1.5s linear infinite; -ms-animation: spin 1.5s linear infinite;
-webkit-animation: spin 1.5s linear infinite; -webkit-animation: spin 1.5s linear infinite;
animation: spin 1.5s linear infinite; animation: spin 1.5s linear infinite;
} }
@-webkit-keyframes spin { @-webkit-keyframes spin {
0% { 0% {
-webkit-transform: rotate(0deg); -webkit-transform: rotate(0deg);
-ms-transform: rotate(0deg); -ms-transform: rotate(0deg);
transform: rotate(0deg); transform: rotate(0deg);
} }
100% { 100% {
-webkit-transform: rotate(360deg); -webkit-transform: rotate(360deg);
-ms-transform: rotate(360deg); -ms-transform: rotate(360deg);
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
@keyframes spin { @keyframes spin {
0% { 0% {
-webkit-transform: rotate(0deg); -webkit-transform: rotate(0deg);
-ms-transform: rotate(0deg); -ms-transform: rotate(0deg);
transform: rotate(0deg); transform: rotate(0deg);
} }
100% { 100% {
-webkit-transform: rotate(360deg); -webkit-transform: rotate(360deg);
-ms-transform: rotate(360deg); -ms-transform: rotate(360deg);
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
#loader-wrapper .loader-section { #loader-wrapper .loader-section {
position: fixed; position: fixed;
top: 0; top: 0;
width: 51%; width: 51%;
height: 100%; height: 100%;
background: #7171C6; background: #7171C6;
z-index: 1000; z-index: 1000;
-webkit-transform: translateX(0); -webkit-transform: translateX(0);
-ms-transform: translateX(0); -ms-transform: translateX(0);
transform: translateX(0); transform: translateX(0);
} }
#loader-wrapper .loader-section.section-left { #loader-wrapper .loader-section.section-left {
left: 0; left: 0;
} }
#loader-wrapper .loader-section.section-right { #loader-wrapper .loader-section.section-right {
right: 0; right: 0;
} }
.loaded #loader-wrapper .loader-section.section-left { .loaded #loader-wrapper .loader-section.section-left {
-webkit-transform: translateX(-100%); -webkit-transform: translateX(-100%);
-ms-transform: translateX(-100%); -ms-transform: translateX(-100%);
transform: translateX(-100%); transform: translateX(-100%);
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000); -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000); transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
} }
.loaded #loader-wrapper .loader-section.section-right { .loaded #loader-wrapper .loader-section.section-right {
-webkit-transform: translateX(100%); -webkit-transform: translateX(100%);
-ms-transform: translateX(100%); -ms-transform: translateX(100%);
transform: translateX(100%); transform: translateX(100%);
-webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000); -webkit-transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000); transition: all 0.7s 0.3s cubic-bezier(0.645, 0.045, 0.355, 1.000);
} }
.loaded #loader { .loaded #loader {
opacity: 0; opacity: 0;
-webkit-transition: all 0.3s ease-out; -webkit-transition: all 0.3s ease-out;
transition: all 0.3s ease-out; transition: all 0.3s ease-out;
} }
.loaded #loader-wrapper { .loaded #loader-wrapper {
visibility: hidden; visibility: hidden;
-webkit-transform: translateY(-100%); -webkit-transform: translateY(-100%);
-ms-transform: translateY(-100%); -ms-transform: translateY(-100%);
transform: translateY(-100%); transform: translateY(-100%);
-webkit-transition: all 0.3s 1s ease-out; -webkit-transition: all 0.3s 1s ease-out;
transition: all 0.3s 1s ease-out; transition: all 0.3s 1s ease-out;
} }
.no-js #loader-wrapper { .no-js #loader-wrapper {
display: none; display: none;
} }
.no-js h1 { .no-js h1 {
color: #222222; color: #222222;
} }
#loader-wrapper .load_title { #loader-wrapper .load_title {
font-family: 'Open Sans'; font-family: 'Open Sans';
color: #FFF; color: #FFF;
font-size: 19px; font-size: 19px;
width: 100%; width: 100%;
text-align: center; text-align: center;
z-index: 9999999999999; z-index: 9999999999999;
position: absolute; position: absolute;
top: 60%; top: 60%;
opacity: 1; opacity: 1;
line-height: 30px; line-height: 30px;
} }
#loader-wrapper .load_title span { #loader-wrapper .load_title span {
font-weight: normal; font-weight: normal;
font-style: italic; font-style: italic;
font-size: 13px; font-size: 13px;
color: #FFF; color: #FFF;
opacity: 0.5; opacity: 0.5;
} }
</style> </style>
</head> </head>
<body> <body>
<div id="app"> <div id="app">
<div id="loader-wrapper"> <div id="loader-wrapper">
<div id="loader"></div> <div id="loader"></div>
<div class="loader-section section-left"></div> <div class="loader-section section-left"></div>
<div class="loader-section section-right"></div> <div class="loader-section section-right"></div>
<div class="load_title">正在加载系统资源,请耐心等待</div> <div class="load_title">正在加载系统资源,请耐心等待</div>
</div> </div>
</div> </div>
</body> </body>
</html> </html>

View File

@@ -1,2 +1,2 @@
User-agent: * User-agent: *
Disallow: / Disallow: /

View File

@@ -1,20 +1,20 @@
<template> <template>
<div id="app"> <div id="app">
<router-view /> <router-view />
<theme-picker /> <theme-picker />
</div> </div>
</template> </template>
<script> <script>
import ThemePicker from "@/components/ThemePicker" import ThemePicker from "@/components/ThemePicker"
export default { export default {
name: "App", name: "App",
components: { ThemePicker } components: { ThemePicker }
} }
</script> </script>
<style scoped> <style scoped>
#app .theme-picker { #app .theme-picker {
display: none; display: none;
} }
</style> </style>

View File

@@ -10,14 +10,14 @@ export function login(username, password, code, uuid) {
uuid uuid
}, ['password'], process.env.VUE_APP_PASSWORD_TRANSFER_KEY) }, ['password'], process.env.VUE_APP_PASSWORD_TRANSFER_KEY)
return request({ return request({
url: '/login', url: '/login',
headers: { headers: {
isToken: false, isToken: false,
repeatSubmit: false repeatSubmit: false
}, },
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 注册方法 // 注册方法
@@ -25,47 +25,38 @@ export function register(data) {
const payload = encryptPasswordFields(data, ['password'], process.env.VUE_APP_PASSWORD_TRANSFER_KEY) const payload = encryptPasswordFields(data, ['password'], process.env.VUE_APP_PASSWORD_TRANSFER_KEY)
return request({ return request({
url: '/register', url: '/register',
headers: { headers: {
isToken: false isToken: false
}, },
method: 'post', method: 'post',
data: payload data: payload
}) })
} }
// 获取用户详细信息 // 获取用户详细信息
export function getInfo() { export function getInfo() {
return request({ return request({
url: '/getInfo', url: '/getInfo',
method: 'get' method: 'get'
}) })
} }
// 解锁屏幕 // 退出方法
export function unlockScreen(password) { export function logout() {
return request({ return request({
url: '/unlockscreen', url: '/logout',
method: 'post', method: 'post'
data: { password } })
}) }
}
// 获取验证码
// 退出方法 export function getCodeImg() {
export function logout() { return request({
return request({ url: '/captchaImage',
url: '/logout', headers: {
method: 'post' isToken: false
}) },
} method: 'get',
timeout: 20000
// 获取验证码 })
export function getCodeImg() {
return request({
url: '/captchaImage',
headers: {
isToken: false
},
method: 'get',
timeout: 20000
})
} }

View File

@@ -1,9 +1,9 @@
import request from '@/utils/request' import request from '@/utils/request'
// 获取路由 // 获取路由
export const getRouters = () => { export const getRouters = () => {
return request({ return request({
url: '/getRouters', url: '/getRouters',
method: 'get' method: 'get'
}) })
} }

View File

@@ -1,57 +1,57 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询缓存详细 // 查询缓存概览
export function getCache() { export function getCache() {
return request({ return request({
url: '/monitor/cache', url: '/monitor/cache',
method: 'get' method: 'get'
}) })
} }
// 查询缓存名称列表 // 查询缓存名称列表
export function listCacheName() { export function listCacheName() {
return request({ return request({
url: '/monitor/cache/getNames', url: '/monitor/cache/getNames',
method: 'get' method: 'get'
}) })
} }
// 查询缓存键名列表 // 查询缓存键名列表
export function listCacheKey(cacheName) { export function listCacheKey(cacheName) {
return request({ return request({
url: '/monitor/cache/getKeys/' + cacheName, url: '/monitor/cache/getKeys/' + cacheName,
method: 'get' method: 'get'
}) })
} }
// 查询缓存内容 // 查询缓存内容
export function getCacheValue(cacheName, cacheKey) { export function getCacheValue(cacheName, cacheKey) {
return request({ return request({
url: '/monitor/cache/getValue/' + cacheName + '/' + cacheKey, url: '/monitor/cache/getValue/' + cacheName + '/' + cacheKey,
method: 'get' method: 'get'
}) })
} }
// 清理指定名称缓存 // 清理指定名称下的缓存
export function clearCacheName(cacheName) { export function clearCacheName(cacheName) {
return request({ return request({
url: '/monitor/cache/clearCacheName/' + cacheName, url: '/monitor/cache/clearCacheName/' + cacheName,
method: 'delete' method: 'delete'
}) })
} }
// 清理指定键名缓存 // 清理指定键名缓存
export function clearCacheKey(cacheKey) { export function clearCacheKey(cacheKey) {
return request({ return request({
url: '/monitor/cache/clearCacheKey/' + cacheKey, url: '/monitor/cache/clearCacheKey/' + cacheKey,
method: 'delete' method: 'delete'
}) })
} }
// 清理全部缓存 // 清理全部缓存
export function clearCacheAll() { export function clearCacheAll() {
return request({ return request({
url: '/monitor/cache/clearCacheAll', url: '/monitor/cache/clearCacheAll',
method: 'delete' method: 'delete'
}) })
} }

View File

@@ -1,34 +1,34 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询登录日志列表 // 查询登录日志列表
export function list(query) { export function list(query) {
return request({ return request({
url: '/monitor/logininfor/list', url: '/monitor/logininfor/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 删除登录日志 // 删除登录日志
export function delLogininfor(infoId) { export function delLogininfor(infoId) {
return request({ return request({
url: '/monitor/logininfor/' + infoId, url: '/monitor/logininfor/' + infoId,
method: 'delete' method: 'delete'
}) })
} }
// 解锁用户登录状态 // 解锁用户登录状态
export function unlockLogininfor(userName) { export function unlockLogininfor(userName) {
return request({ return request({
url: '/monitor/logininfor/unlock/' + userName, url: '/monitor/logininfor/unlock/' + userName,
method: 'get' method: 'get'
}) })
} }
// 清空登录日志 // 清空登录日志
export function cleanLogininfor() { export function cleanLogininfor() {
return request({ return request({
url: '/monitor/logininfor/clean', url: '/monitor/logininfor/clean',
method: 'delete' method: 'delete'
}) })
} }

View File

@@ -1,18 +1,18 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询在线用户列表 // 查询在线用户列表
export function list(query) { export function list(query) {
return request({ return request({
url: '/monitor/online/list', url: '/monitor/online/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 强退用户 // 强退用户
export function forceLogout(tokenId) { export function forceLogout(tokenId) {
return request({ return request({
url: '/monitor/online/' + tokenId, url: '/monitor/online/' + tokenId,
method: 'delete' method: 'delete'
}) })
} }

View File

@@ -1,26 +1,26 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询操作日志列表 // 查询操作日志列表
export function list(query) { export function list(query) {
return request({ return request({
url: '/monitor/operlog/list', url: '/monitor/operlog/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 删除操作日志 // 删除操作日志
export function delOperlog(operId) { export function delOperlog(operId) {
return request({ return request({
url: '/monitor/operlog/' + operId, url: '/monitor/operlog/' + operId,
method: 'delete' method: 'delete'
}) })
} }
// 清空操作日志 // 清空操作日志
export function cleanOperlog() { export function cleanOperlog() {
return request({ return request({
url: '/monitor/operlog/clean', url: '/monitor/operlog/clean',
method: 'delete' method: 'delete'
}) })
} }

View File

@@ -1,9 +1,9 @@
import request from '@/utils/request' import request from '@/utils/request'
// 获取服务信息 // 获取服务信息
export function getServer() { export function getServer() {
return request({ return request({
url: '/monitor/server', url: '/monitor/server',
method: 'get' method: 'get'
}) })
} }

View File

@@ -1,60 +1,60 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询参数列表 // 查询参数列表
export function listConfig(query) { export function listConfig(query) {
return request({ return request({
url: '/system/config/list', url: '/system/config/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询参数详细 // 查询参数详细
export function getConfig(configId) { export function getConfig(configId) {
return request({ return request({
url: '/system/config/' + configId, url: '/system/config/' + configId,
method: 'get' method: 'get'
}) })
} }
// 根据参数键名查询参数值 // 根据参数键名查询参数值
export function getConfigKey(configKey) { export function getConfigKey(configKey) {
return request({ return request({
url: '/system/config/configKey/' + configKey, url: '/system/config/configKey/' + configKey,
method: 'get' method: 'get'
}) })
} }
// 新增参数配置 // 新增参数配置
export function addConfig(data) { export function addConfig(data) {
return request({ return request({
url: '/system/config', url: '/system/config',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 修改参数配置 // 修改参数配置
export function updateConfig(data) { export function updateConfig(data) {
return request({ return request({
url: '/system/config', url: '/system/config',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 删除参数配置 // 删除参数配置
export function delConfig(configId) { export function delConfig(configId) {
return request({ return request({
url: '/system/config/' + configId, url: '/system/config/' + configId,
method: 'delete' method: 'delete'
}) })
} }
// 刷新参数缓存 // 刷新参数缓存
export function refreshCache() { export function refreshCache() {
return request({ return request({
url: '/system/config/refreshCache', url: '/system/config/refreshCache',
method: 'delete' method: 'delete'
}) })
} }

View File

@@ -1,61 +1,52 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询部门列表 // 查询部门列表
export function listDept(query) { export function listDept(query) {
return request({ return request({
url: '/system/dept/list', url: '/system/dept/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询部门列表(排除节点) // 查询部门列表(排除节点)
export function listDeptExcludeChild(deptId) { export function listDeptExcludeChild(deptId) {
return request({ return request({
url: '/system/dept/list/exclude/' + deptId, url: '/system/dept/list/exclude/' + deptId,
method: 'get' method: 'get'
}) })
} }
// 查询部门详细 // 查询部门详细
export function getDept(deptId) { export function getDept(deptId) {
return request({ return request({
url: '/system/dept/' + deptId, url: '/system/dept/' + deptId,
method: 'get' method: 'get'
}) })
} }
// 新增部门 // 新增部门
export function addDept(data) { export function addDept(data) {
return request({ return request({
url: '/system/dept', url: '/system/dept',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 修改部门 // 修改部门
export function updateDept(data) { export function updateDept(data) {
return request({ return request({
url: '/system/dept', url: '/system/dept',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 保存部门排序 // 删除部门
export function updateDeptSort(data) { export function delDept(deptId) {
return request({ return request({
url: '/system/dept/updateSort', url: '/system/dept/' + deptId,
method: 'put', method: 'delete'
data: data })
})
}
// 删除部门
export function delDept(deptId) {
return request({
url: '/system/dept/' + deptId,
method: 'delete'
})
} }

View File

@@ -1,52 +1,52 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询字典数据列表 // 查询字典数据列表
export function listData(query) { export function listData(query) {
return request({ return request({
url: '/system/dict/data/list', url: '/system/dict/data/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询字典数据详细 // 查询字典数据详细
export function getData(dictCode) { export function getData(dictCode) {
return request({ return request({
url: '/system/dict/data/' + dictCode, url: '/system/dict/data/' + dictCode,
method: 'get' method: 'get'
}) })
} }
// 根据字典类型查询字典数据信息 // 根据字典类型查询字典数据信息
export function getDicts(dictType) { export function getDicts(dictType) {
return request({ return request({
url: '/system/dict/data/type/' + dictType, url: '/system/dict/data/type/' + dictType,
method: 'get' method: 'get'
}) })
} }
// 新增字典数据 // 新增字典数据
export function addData(data) { export function addData(data) {
return request({ return request({
url: '/system/dict/data', url: '/system/dict/data',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 修改字典数据 // 修改字典数据
export function updateData(data) { export function updateData(data) {
return request({ return request({
url: '/system/dict/data', url: '/system/dict/data',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 删除字典数据 // 删除字典数据
export function delData(dictCode) { export function delData(dictCode) {
return request({ return request({
url: '/system/dict/data/' + dictCode, url: '/system/dict/data/' + dictCode,
method: 'delete' method: 'delete'
}) })
} }

View File

@@ -1,60 +1,60 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询字典类型列表 // 查询字典类型列表
export function listType(query) { export function listType(query) {
return request({ return request({
url: '/system/dict/type/list', url: '/system/dict/type/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询字典类型详细 // 查询字典类型详细
export function getType(dictId) { export function getType(dictId) {
return request({ return request({
url: '/system/dict/type/' + dictId, url: '/system/dict/type/' + dictId,
method: 'get' method: 'get'
}) })
} }
// 新增字典类型 // 新增字典类型
export function addType(data) { export function addType(data) {
return request({ return request({
url: '/system/dict/type', url: '/system/dict/type',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 修改字典类型 // 修改字典类型
export function updateType(data) { export function updateType(data) {
return request({ return request({
url: '/system/dict/type', url: '/system/dict/type',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 删除字典类型 // 删除字典类型
export function delType(dictId) { export function delType(dictId) {
return request({ return request({
url: '/system/dict/type/' + dictId, url: '/system/dict/type/' + dictId,
method: 'delete' method: 'delete'
}) })
} }
// 刷新字典缓存 // 刷新字典缓存
export function refreshCache() { export function refreshCache() {
return request({ return request({
url: '/system/dict/type/refreshCache', url: '/system/dict/type/refreshCache',
method: 'delete' method: 'delete'
}) })
} }
// 获取字典选择框列表 // 获取字典选择框列表
export function optionselect() { export function optionselect() {
return request({ return request({
url: '/system/dict/type/optionselect', url: '/system/dict/type/optionselect',
method: 'get' method: 'get'
}) })
} }

View File

@@ -1,69 +1,60 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询菜单列表 // 查询菜单列表
export function listMenu(query) { export function listMenu(query) {
return request({ return request({
url: '/system/menu/list', url: '/system/menu/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询菜单详细 // 查询菜单详细
export function getMenu(menuId) { export function getMenu(menuId) {
return request({ return request({
url: '/system/menu/' + menuId, url: '/system/menu/' + menuId,
method: 'get' method: 'get'
}) })
} }
// 查询菜单下拉树结构 // 查询菜单下拉树结构
export function treeselect() { export function treeselect() {
return request({ return request({
url: '/system/menu/treeselect', url: '/system/menu/treeselect',
method: 'get' method: 'get'
}) })
} }
// 根据角色ID查询菜单下拉树结构 // 根据角色ID查询菜单下拉树结构
export function roleMenuTreeselect(roleId) { export function roleMenuTreeselect(roleId) {
return request({ return request({
url: '/system/menu/roleMenuTreeselect/' + roleId, url: '/system/menu/roleMenuTreeselect/' + roleId,
method: 'get' method: 'get'
}) })
} }
// 新增菜单 // 新增菜单
export function addMenu(data) { export function addMenu(data) {
return request({ return request({
url: '/system/menu', url: '/system/menu',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 修改菜单 // 修改菜单
export function updateMenu(data) { export function updateMenu(data) {
return request({ return request({
url: '/system/menu', url: '/system/menu',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 保存菜单排序 // 删除菜单
export function updateMenuSort(data) { export function delMenu(menuId) {
return request({ return request({
url: '/system/menu/updateSort', url: '/system/menu/' + menuId,
method: 'put', method: 'delete'
data: data })
})
}
// 删除菜单
export function delMenu(menuId) {
return request({
url: '/system/menu/' + menuId,
method: 'delete'
})
} }

View File

@@ -1,79 +1,44 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询公告列表 // 查询公告列表
export function listNotice(query) { export function listNotice(query) {
return request({ return request({
url: '/system/notice/list', url: '/system/notice/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询公告详细 // 查询公告详细
export function getNotice(noticeId) { export function getNotice(noticeId) {
return request({ return request({
url: '/system/notice/' + noticeId, url: '/system/notice/' + noticeId,
method: 'get' method: 'get'
}) })
} }
// 新增公告 // 新增公告
export function addNotice(data) { export function addNotice(data) {
return request({ return request({
url: '/system/notice', url: '/system/notice',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 修改公告 // 修改公告
export function updateNotice(data) { export function updateNotice(data) {
return request({ return request({
url: '/system/notice', url: '/system/notice',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 删除公告 // 删除公告
export function delNotice(noticeId) { export function delNotice(noticeId) {
return request({ return request({
url: '/system/notice/' + noticeId, url: '/system/notice/' + noticeId,
method: 'delete' method: 'delete'
}) })
} }
// 首页顶部公告列表(带已读状态)
export function listNoticeTop() {
return request({
url: '/system/notice/listTop',
method: 'get'
})
}
// 标记公告已读
export function markNoticeRead(noticeId) {
return request({
url: '/system/notice/markRead',
method: 'post',
params: { noticeId }
})
}
// 批量标记已读
export function markNoticeReadAll(ids) {
return request({
url: '/system/notice/markReadAll',
method: 'post',
params: { ids }
})
}
// 查询公告已读用户列表
export function listNoticeReadUsers(query) {
return request({
url: '/system/notice/readUsers/list',
method: 'get',
params: query
})
}

View File

@@ -1,44 +1,44 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询岗位列表 // 查询岗位列表
export function listPost(query) { export function listPost(query) {
return request({ return request({
url: '/system/post/list', url: '/system/post/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询岗位详细 // 查询岗位详细
export function getPost(postId) { export function getPost(postId) {
return request({ return request({
url: '/system/post/' + postId, url: '/system/post/' + postId,
method: 'get' method: 'get'
}) })
} }
// 新增岗位 // 新增岗位
export function addPost(data) { export function addPost(data) {
return request({ return request({
url: '/system/post', url: '/system/post',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 修改岗位 // 修改岗位
export function updatePost(data) { export function updatePost(data) {
return request({ return request({
url: '/system/post', url: '/system/post',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 删除岗位 // 删除岗位
export function delPost(postId) { export function delPost(postId) {
return request({ return request({
url: '/system/post/' + postId, url: '/system/post/' + postId,
method: 'delete' method: 'delete'
}) })
} }

View File

@@ -1,119 +1,119 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询角色列表 // 查询角色列表
export function listRole(query) { export function listRole(query) {
return request({ return request({
url: '/system/role/list', url: '/system/role/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询角色详细 // 查询角色详细
export function getRole(roleId) { export function getRole(roleId) {
return request({ return request({
url: '/system/role/' + roleId, url: '/system/role/' + roleId,
method: 'get' method: 'get'
}) })
} }
// 新增角色 // 新增角色
export function addRole(data) { export function addRole(data) {
return request({ return request({
url: '/system/role', url: '/system/role',
method: 'post', method: 'post',
data: data data: data
}) })
} }
// 修改角色 // 修改角色
export function updateRole(data) { export function updateRole(data) {
return request({ return request({
url: '/system/role', url: '/system/role',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 角色数据权限 // 角色数据权限
export function dataScope(data) { export function dataScope(data) {
return request({ return request({
url: '/system/role/dataScope', url: '/system/role/dataScope',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 角色状态修改 // 角色状态修改
export function changeRoleStatus(roleId, status) { export function changeRoleStatus(roleId, status) {
const data = { const data = {
roleId, roleId,
status status
} }
return request({ return request({
url: '/system/role/changeStatus', url: '/system/role/changeStatus',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 删除角色 // 删除角色
export function delRole(roleId) { export function delRole(roleId) {
return request({ return request({
url: '/system/role/' + roleId, url: '/system/role/' + roleId,
method: 'delete' method: 'delete'
}) })
} }
// 查询角色已授权用户列表 // 查询角色已授权用户列表
export function allocatedUserList(query) { export function allocatedUserList(query) {
return request({ return request({
url: '/system/role/authUser/allocatedList', url: '/system/role/authUser/allocatedList',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询角色未授权用户列表 // 查询角色未授权用户列表
export function unallocatedUserList(query) { export function unallocatedUserList(query) {
return request({ return request({
url: '/system/role/authUser/unallocatedList', url: '/system/role/authUser/unallocatedList',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 取消用户授权角色 // 取消用户授权角色
export function authUserCancel(data) { export function authUserCancel(data) {
return request({ return request({
url: '/system/role/authUser/cancel', url: '/system/role/authUser/cancel',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 批量取消用户授权角色 // 批量取消用户授权角色
export function authUserCancelAll(data) { export function authUserCancelAll(data) {
return request({ return request({
url: '/system/role/authUser/cancelAll', url: '/system/role/authUser/cancelAll',
method: 'put', method: 'put',
params: data params: data
}) })
} }
// 授权用户选择 // 授权用户选择
export function authUserSelectAll(data) { export function authUserSelectAll(data) {
return request({ return request({
url: '/system/role/authUser/selectAll', url: '/system/role/authUser/selectAll',
method: 'put', method: 'put',
params: data params: data
}) })
} }
// 根据角色ID查询部门树结构 // 根据角色ID查询部门树结构
export function deptTreeSelect(roleId) { export function deptTreeSelect(roleId) {
return request({ return request({
url: '/system/role/deptTree/' + roleId, url: '/system/role/deptTree/' + roleId,
method: 'get' method: 'get'
}) })
} }

View File

@@ -1,25 +1,25 @@
import request from '@/utils/request' import request from '@/utils/request'
import { parseStrEmpty } from "@/utils/ruoyi" import { parseStrEmpty } from "@/utils/ruoyi";
import { encryptPasswordFields } from '@/utils/passwordTransfer' import { encryptPasswordFields } from '@/utils/passwordTransfer'
// 查询用户列表 // 查询用户列表
export function listUser(query) { export function listUser(query) {
return request({ return request({
url: '/system/user/list', url: '/system/user/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询用户详细 // 查询用户详细
export function getUser(userId) { export function getUser(userId) {
return request({ return request({
url: '/system/user/' + parseStrEmpty(userId), url: '/system/user/' + parseStrEmpty(userId),
method: 'get' method: 'get'
}) })
} }
// 新增用户 // 新增用户
export function addUser(data) { export function addUser(data) {
const payload = encryptPasswordFields(data, ['password'], process.env.VUE_APP_PASSWORD_TRANSFER_KEY) const payload = encryptPasswordFields(data, ['password'], process.env.VUE_APP_PASSWORD_TRANSFER_KEY)
return request({ return request({
@@ -28,111 +28,111 @@ export function addUser(data) {
data: payload data: payload
}) })
} }
// 修改用户 // 修改用户
export function updateUser(data) { export function updateUser(data) {
return request({ return request({
url: '/system/user', url: '/system/user',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 删除用户 // 删除用户
export function delUser(userId) { export function delUser(userId) {
return request({ return request({
url: '/system/user/' + userId, url: '/system/user/' + userId,
method: 'delete' method: 'delete'
}) })
} }
// 用户密码重置 // 用户密码重置
export function resetUserPwd(userId, password) { export function resetUserPwd(userId, password) {
const data = encryptPasswordFields({ const data = encryptPasswordFields({
userId, userId,
password password
}, ['password'], process.env.VUE_APP_PASSWORD_TRANSFER_KEY) }, ['password'], process.env.VUE_APP_PASSWORD_TRANSFER_KEY)
return request({ return request({
url: '/system/user/resetPwd', url: '/system/user/resetPwd',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 用户状态修改 // 用户状态修改
export function changeUserStatus(userId, status) { export function changeUserStatus(userId, status) {
const data = { const data = {
userId, userId,
status status
} }
return request({ return request({
url: '/system/user/changeStatus', url: '/system/user/changeStatus',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 查询用户个人信息 // 查询用户个人信息
export function getUserProfile() { export function getUserProfile() {
return request({ return request({
url: '/system/user/profile', url: '/system/user/profile',
method: 'get' method: 'get'
}) })
} }
// 修改用户个人信息 // 修改用户个人信息
export function updateUserProfile(data) { export function updateUserProfile(data) {
return request({ return request({
url: '/system/user/profile', url: '/system/user/profile',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 用户密码重置 // 用户密码重置
export function updateUserPwd(oldPassword, newPassword) { export function updateUserPwd(oldPassword, newPassword) {
const data = encryptPasswordFields({ const data = encryptPasswordFields({
oldPassword, oldPassword,
newPassword newPassword
}, ['oldPassword', 'newPassword'], process.env.VUE_APP_PASSWORD_TRANSFER_KEY) }, ['oldPassword', 'newPassword'], process.env.VUE_APP_PASSWORD_TRANSFER_KEY)
return request({ return request({
url: '/system/user/profile/updatePwd', url: '/system/user/profile/updatePwd',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 用户头像上传 // 用户头像上传
export function uploadAvatar(data) { export function uploadAvatar(data) {
return request({ return request({
url: '/system/user/profile/avatar', url: '/system/user/profile/avatar',
method: 'post', method: 'post',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
data: data data: data
}) })
} }
// 查询授权角色 // 查询授权角色
export function getAuthRole(userId) { export function getAuthRole(userId) {
return request({ return request({
url: '/system/user/authRole/' + userId, url: '/system/user/authRole/' + userId,
method: 'get' method: 'get'
}) })
} }
// 保存授权角色 // 保存授权角色
export function updateAuthRole(data) { export function updateAuthRole(data) {
return request({ return request({
url: '/system/user/authRole', url: '/system/user/authRole',
method: 'put', method: 'put',
params: data params: data
}) })
} }
// 查询部门下拉树结构 // 查询部门下拉树结构
export function deptTreeSelect() { export function deptTreeSelect() {
return request({ return request({
url: '/system/user/deptTree', url: '/system/user/deptTree',
method: 'get' method: 'get'
}) })
} }

View File

@@ -1,85 +1,85 @@
import request from '@/utils/request' import request from '@/utils/request'
// 查询生成表数据 // 查询生成表数据
export function listTable(query) { export function listTable(query) {
return request({ return request({
url: '/tool/gen/list', url: '/tool/gen/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询db数据库列表 // 查询db数据库列表
export function listDbTable(query) { export function listDbTable(query) {
return request({ return request({
url: '/tool/gen/db/list', url: '/tool/gen/db/list',
method: 'get', method: 'get',
params: query params: query
}) })
} }
// 查询表详细信息 // 查询表详细信息
export function getGenTable(tableId) { export function getGenTable(tableId) {
return request({ return request({
url: '/tool/gen/' + tableId, url: '/tool/gen/' + tableId,
method: 'get' method: 'get'
}) })
} }
// 修改代码生成信息 // 修改代码生成信息
export function updateGenTable(data) { export function updateGenTable(data) {
return request({ return request({
url: '/tool/gen', url: '/tool/gen',
method: 'put', method: 'put',
data: data data: data
}) })
} }
// 导入表 // 导入表
export function importTable(data) { export function importTable(data) {
return request({ return request({
url: '/tool/gen/importTable', url: '/tool/gen/importTable',
method: 'post', method: 'post',
params: data params: data
}) })
} }
// 创建表 // 创建表
export function createTable(data) { export function createTable(data) {
return request({ return request({
url: '/tool/gen/createTable', url: '/tool/gen/createTable',
method: 'post', method: 'post',
params: data params: data
}) })
} }
// 预览生成代码 // 预览生成代码
export function previewTable(tableId) { export function previewTable(tableId) {
return request({ return request({
url: '/tool/gen/preview/' + tableId, url: '/tool/gen/preview/' + tableId,
method: 'get' method: 'get'
}) })
} }
// 删除表数据 // 删除表数据
export function delTable(tableId) { export function delTable(tableId) {
return request({ return request({
url: '/tool/gen/' + tableId, url: '/tool/gen/' + tableId,
method: 'delete' method: 'delete'
}) })
} }
// 生成代码(自定义路径) // 生成代码(自定义路径)
export function genCode(tableName) { export function genCode(tableName) {
return request({ return request({
url: '/tool/gen/genCode/' + tableName, url: '/tool/gen/genCode/' + tableName,
method: 'get' method: 'get'
}) })
} }
// 同步数据库 // 同步数据库
export function synchDb(tableName) { export function synchDb(tableName) {
return request({ return request({
url: '/tool/gen/synchDb/' + tableName, url: '/tool/gen/synchDb/' + tableName,
method: 'get' method: 'get'
}) })
} }

View File

@@ -1,9 +1,9 @@
import Vue from 'vue' import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon'// svg component import SvgIcon from '@/components/SvgIcon'// svg component
// register globally // register globally
Vue.component('svg-icon', SvgIcon) Vue.component('svg-icon', SvgIcon)
const req = require.context('./svg', false, /\.svg$/) const req = require.context('./svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys().map(requireContext) const requireAll = requireContext => requireContext.keys().map(requireContext)
requireAll(req) requireAll(req)

View File

@@ -1 +0,0 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1773923748724" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5930" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M512 212l48.8 12c101.6 24.8 176 117.6 176 220.8v254.4l18.4 18.4 24.8 25.6h-536l24.8-25.6 18.4-18.4V444.8c0-103.2 73.6-196.8 176-220.8l48.8-12M512 64c-36.8 0-64 30.4-64 68v30.4C320.8 192 223.2 307.2 223.2 444.8v228.8L136 763.2v44.8h752v-44.8l-87.2-89.6V444.8c0-137.6-97.6-252.8-224.8-283.2v-28.8c0-32-17.6-60.8-48-67.2-5.6-1.6-11.2-1.6-16-1.6z m88 808H424c0 49.6 38.4 88 88 88s88-38.4 88-88z" p-id="5931"></path></svg>

Before

Width:  |  Height:  |  Size: 750 B

View File

@@ -1,2 +1,2 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1543827393750" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4695" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; } <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1543827393750" class="icon" style="" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4695" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; }
</style></defs><path d="M64 64V640H896V64H64zM0 0h960v704H0V0z" p-id="4696"></path><path d="M192 896H768v64H192zM448 640H512v256h-64z" p-id="4697"></path><path d="M479.232 561.604267l309.9904-348.330667-47.803733-42.5472-259.566934 291.669333L303.957333 240.008533 163.208533 438.6048l52.224 37.009067 91.6224-129.28z" p-id="4698"></path></svg> </style></defs><path d="M64 64V640H896V64H64zM0 0h960v704H0V0z" p-id="4696"></path><path d="M192 896H768v64H192zM448 640H512v256h-64z" p-id="4697"></path><path d="M479.232 561.604267l309.9904-348.330667-47.803733-42.5472-259.566934 291.669333L303.957333 240.008533 163.208533 438.6048l52.224 37.009067 91.6224-129.28z" p-id="4698"></path></svg>

Before

Width:  |  Height:  |  Size: 884 B

After

Width:  |  Height:  |  Size: 883 B

View File

@@ -1,2 +1,2 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1543827724451" class="icon" style="" viewBox="0 0 1084 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10233" xmlns:xlink="http://www.w3.org/1999/xlink" width="211.71875" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; } <?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1543827724451" class="icon" style="" viewBox="0 0 1084 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10233" xmlns:xlink="http://www.w3.org/1999/xlink" width="211.71875" height="200"><defs><style type="text/css">@font-face { font-family: rbicon; src: url("chrome-extension://dipiagiiohfljcicegpgffpbnjmgjcnf/fonts/rbicon.woff2") format("woff2"); font-weight: normal; font-style: normal; }
</style></defs><path d="M1080.09609 434.500756c-4.216302-23.731757-26.9241-47.945376-50.595623-53.185637l-17.648235-4.095836a175.940257 175.940257 0 0 1-101.612877-80.832531 177.807476 177.807476 0 0 1-18.732427-129.801867l5.541425-16.684509c7.10748-23.129428-2.108151-54.992624-20.599646-70.833873 0 0-16.624276-14.094495-63.244529-41.199293-46.800951-26.984332-66.858502-34.513443-66.858502-34.513443-22.76803-8.372371-54.631227-0.361397-71.255503 17.407304l-12.287509 13.251234a173.470708 173.470708 0 0 1-120.465769 48.065842A174.13327 174.13327 0 0 1 421.329029 33.590675L409.583617 20.761071C393.140039 2.99237 361.096144-4.898138 338.267881 3.353767c0 0-20.358715 7.529111-67.099434 34.513443-46.800951 27.34573-63.244529 41.440225-63.244529 41.440225-18.431263 15.66055-27.646894 47.222582-20.539413 70.592941l5.059562 16.865207a178.048407 178.048407 0 0 1-18.672194 129.621169 174.916297 174.916297 0 0 1-102.275439 81.073463l-17.045906 3.854904c-23.310126 5.42096-46.258856 29.333415-50.595623 53.185637 0 0-3.854905 21.382674-3.854905 75.712737 0 54.330062 3.854905 75.712736 3.854905 75.712736 4.216302 23.972688 26.9241 47.945376 50.595623 53.185637l16.624276 3.854905a174.253736 174.253736 0 0 1 102.395904 81.314394c23.310126 40.837896 28.911785 87.337683 18.732427 129.801867l-4.81863 16.443578c-7.10748 23.129428 2.108151 54.992624 20.599646 70.833872 0 0 16.624276 14.094495 63.244529 41.199293 46.800951 27.104798 66.918735 34.513443 66.918735 34.513443 22.707798 8.372371 54.631227 0.361397 71.255503-17.407303l11.624947-12.588673a175.096996 175.096996 0 0 1 242.256662 0.120465l11.624947 12.648906c16.383345 17.708468 48.427239 25.598976 71.255503 17.347071 0 0 20.358715-7.529111 67.159666-34.513443 46.740719-27.104798 63.124063-41.199293 63.124064-41.199293 18.491496-15.600317 27.707127-47.463513 20.599646-70.833873l-5.059562-17.106139a176.723284 176.723284 0 0 1 18.672194-129.139305 176.060722 176.060722 0 0 1 102.395904-81.314394l16.68451-3.854905c23.310126-5.42096 46.258856-29.333415 50.595623-53.185637 0 0 3.854905-21.382674 3.854904-75.712737-0.240932-54.330062-4.095836-75.833202-4.095836-75.833202z m-537.819428 293.334149c-119.261112 0-216.175824-97.336342-216.175824-217.621412a216.657687 216.657687 0 0 1 216.236057-217.320249c119.200879 0 216.115591 97.276109 216.11559 217.56118-0.240932 120.044139-96.974945 217.320248-216.175823 217.320249z" p-id="10234"></path></svg> </style></defs><path d="M1080.09609 434.500756c-4.216302-23.731757-26.9241-47.945376-50.595623-53.185637l-17.648235-4.095836a175.940257 175.940257 0 0 1-101.612877-80.832531 177.807476 177.807476 0 0 1-18.732427-129.801867l5.541425-16.684509c7.10748-23.129428-2.108151-54.992624-20.599646-70.833873 0 0-16.624276-14.094495-63.244529-41.199293-46.800951-26.984332-66.858502-34.513443-66.858502-34.513443-22.76803-8.372371-54.631227-0.361397-71.255503 17.407304l-12.287509 13.251234a173.470708 173.470708 0 0 1-120.465769 48.065842A174.13327 174.13327 0 0 1 421.329029 33.590675L409.583617 20.761071C393.140039 2.99237 361.096144-4.898138 338.267881 3.353767c0 0-20.358715 7.529111-67.099434 34.513443-46.800951 27.34573-63.244529 41.440225-63.244529 41.440225-18.431263 15.66055-27.646894 47.222582-20.539413 70.592941l5.059562 16.865207a178.048407 178.048407 0 0 1-18.672194 129.621169 174.916297 174.916297 0 0 1-102.275439 81.073463l-17.045906 3.854904c-23.310126 5.42096-46.258856 29.333415-50.595623 53.185637 0 0-3.854905 21.382674-3.854905 75.712737 0 54.330062 3.854905 75.712736 3.854905 75.712736 4.216302 23.972688 26.9241 47.945376 50.595623 53.185637l16.624276 3.854905a174.253736 174.253736 0 0 1 102.395904 81.314394c23.310126 40.837896 28.911785 87.337683 18.732427 129.801867l-4.81863 16.443578c-7.10748 23.129428 2.108151 54.992624 20.599646 70.833872 0 0 16.624276 14.094495 63.244529 41.199293 46.800951 27.104798 66.918735 34.513443 66.918735 34.513443 22.707798 8.372371 54.631227 0.361397 71.255503-17.407303l11.624947-12.588673a175.096996 175.096996 0 0 1 242.256662 0.120465l11.624947 12.648906c16.383345 17.708468 48.427239 25.598976 71.255503 17.347071 0 0 20.358715-7.529111 67.159666-34.513443 46.740719-27.104798 63.124063-41.199293 63.124064-41.199293 18.491496-15.600317 27.707127-47.463513 20.599646-70.833873l-5.059562-17.106139a176.723284 176.723284 0 0 1 18.672194-129.139305 176.060722 176.060722 0 0 1 102.395904-81.314394l16.68451-3.854905c23.310126-5.42096 46.258856-29.333415 50.595623-53.185637 0 0 3.854905-21.382674 3.854904-75.712737-0.240932-54.330062-4.095836-75.833202-4.095836-75.833202z m-537.819428 293.334149c-119.261112 0-216.175824-97.336342-216.175824-217.621412a216.657687 216.657687 0 0 1 216.236057-217.320249c119.200879 0 216.115591 97.276109 216.11559 217.56118-0.240932 120.044139-96.974945 217.320248-216.175823 217.320249z" p-id="10234"></path></svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@@ -1,22 +1,22 @@
# replace default config # replace default config
# multipass: true # multipass: true
# full: true # full: true
plugins: plugins:
# - name # - name
# #
# or: # or:
# - name: false # - name: false
# - name: true # - name: true
# #
# or: # or:
# - name: # - name:
# param1: 1 # param1: 1
# param2: 2 # param2: 2
- removeAttrs: - removeAttrs:
attrs: attrs:
- 'fill' - 'fill'
- 'fill-rule' - 'fill-rule'

View File

@@ -1,39 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" <svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"> xmlns:xlink="http://www.w3.org/1999/xlink">
<defs> <defs>
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1"> <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix> <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge> <feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode> <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode> <feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge> </feMerge>
</filter> </filter>
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect> <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4"> <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix> <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter> </filter>
</defs> </defs>
<g id="配置面板" width="48" height="40" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="配置面板" width="48" height="40" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="setting-copy-2" width="48" height="40" transform="translate(-1190.000000, -136.000000)"> <g id="setting-copy-2" width="48" height="40" transform="translate(-1190.000000, -136.000000)">
<g id="Group-8" width="48" height="40" transform="translate(1167.000000, 0.000000)"> <g id="Group-8" width="48" height="40" transform="translate(1167.000000, 0.000000)">
<g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)"> <g id="Group-5-Copy-5" filter="url(#filter-1)" transform="translate(25.000000, 137.000000)">
<mask id="mask-3" fill="white"> <mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use> <use xlink:href="#path-2"></use>
</mask> </mask>
<g id="Rectangle-18"> <g id="Rectangle-18">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use> <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use> <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
</g> </g>
<rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect> <rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect>
<rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect> <rect id="Rectangle-18" fill="#303648" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect>
</g> </g>
</g> </g>
</g> </g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1,39 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1" <svg width="52px" height="45px" viewBox="0 0 52 45" version="1.1"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"> xmlns:xlink="http://www.w3.org/1999/xlink">
<defs> <defs>
<filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1"> <filter x="-9.4%" y="-6.2%" width="118.8%" height="122.5%" filterUnits="objectBoundingBox" id="filter-1">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> <feGaussianBlur stdDeviation="1" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix> <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.15 0" type="matrix" in="shadowBlurOuter1" result="shadowMatrixOuter1"></feColorMatrix>
<feMerge> <feMerge>
<feMergeNode in="shadowMatrixOuter1"></feMergeNode> <feMergeNode in="shadowMatrixOuter1"></feMergeNode>
<feMergeNode in="SourceGraphic"></feMergeNode> <feMergeNode in="SourceGraphic"></feMergeNode>
</feMerge> </feMerge>
</filter> </filter>
<rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect> <rect id="path-2" x="0" y="0" width="48" height="40" rx="4"></rect>
<filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4"> <filter x="-4.2%" y="-2.5%" width="108.3%" height="110.0%" filterUnits="objectBoundingBox" id="filter-4">
<feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset> <feOffset dx="0" dy="1" in="SourceAlpha" result="shadowOffsetOuter1"></feOffset>
<feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur> <feGaussianBlur stdDeviation="0.5" in="shadowOffsetOuter1" result="shadowBlurOuter1"></feGaussianBlur>
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix> <feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.1 0" type="matrix" in="shadowBlurOuter1"></feColorMatrix>
</filter> </filter>
</defs> </defs>
<g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> <g id="配置面板" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="setting-copy-2" transform="translate(-1254.000000, -136.000000)"> <g id="setting-copy-2" transform="translate(-1254.000000, -136.000000)">
<g id="Group-8" transform="translate(1167.000000, 0.000000)"> <g id="Group-8" transform="translate(1167.000000, 0.000000)">
<g id="Group-5" filter="url(#filter-1)" transform="translate(89.000000, 137.000000)"> <g id="Group-5" filter="url(#filter-1)" transform="translate(89.000000, 137.000000)">
<mask id="mask-3" fill="white"> <mask id="mask-3" fill="white">
<use xlink:href="#path-2"></use> <use xlink:href="#path-2"></use>
</mask> </mask>
<g id="Rectangle-18"> <g id="Rectangle-18">
<use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use> <use fill="black" fill-opacity="1" filter="url(#filter-4)" xlink:href="#path-2"></use>
<use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use> <use fill="#F0F2F5" fill-rule="evenodd" xlink:href="#path-2"></use>
</g> </g>
<rect id="Rectangle-18" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect> <rect id="Rectangle-18" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="16" height="40"></rect>
<rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect> <rect id="Rectangle-11" fill="#FFFFFF" mask="url(#mask-3)" x="0" y="0" width="48" height="10"></rect>
</g> </g>
</g> </g>
</g> </g>
</g> </g>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1,99 +1,99 @@
@import './variables.scss'; @import './variables.scss';
@mixin colorBtn($color) { @mixin colorBtn($color) {
background: $color; background: $color;
&:hover { &:hover {
color: $color; color: $color;
&:before, &:before,
&:after { &:after {
background: $color; background: $color;
} }
} }
} }
.blue-btn { .blue-btn {
@include colorBtn($blue) @include colorBtn($blue)
} }
.light-blue-btn { .light-blue-btn {
@include colorBtn($light-blue) @include colorBtn($light-blue)
} }
.red-btn { .red-btn {
@include colorBtn($red) @include colorBtn($red)
} }
.pink-btn { .pink-btn {
@include colorBtn($pink) @include colorBtn($pink)
} }
.green-btn { .green-btn {
@include colorBtn($green) @include colorBtn($green)
} }
.tiffany-btn { .tiffany-btn {
@include colorBtn($tiffany) @include colorBtn($tiffany)
} }
.yellow-btn { .yellow-btn {
@include colorBtn($yellow) @include colorBtn($yellow)
} }
.pan-btn { .pan-btn {
font-size: 14px; font-size: 14px;
color: #fff; color: #fff;
padding: 14px 36px; padding: 14px 36px;
border-radius: 8px; border-radius: 8px;
border: none; border: none;
outline: none; outline: none;
transition: 600ms ease all; transition: 600ms ease all;
position: relative; position: relative;
display: inline-block; display: inline-block;
&:hover { &:hover {
background: #fff; background: #fff;
&:before, &:before,
&:after { &:after {
width: 100%; width: 100%;
transition: 600ms ease all; transition: 600ms ease all;
} }
} }
&:before, &:before,
&:after { &:after {
content: ''; content: '';
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
height: 2px; height: 2px;
width: 0; width: 0;
transition: 400ms ease all; transition: 400ms ease all;
} }
&::after { &::after {
right: inherit; right: inherit;
top: inherit; top: inherit;
left: 0; left: 0;
bottom: 0; bottom: 0;
} }
} }
.custom-button { .custom-button {
display: inline-block; display: inline-block;
line-height: 1; line-height: 1;
white-space: nowrap; white-space: nowrap;
cursor: pointer; cursor: pointer;
background: #fff; background: #fff;
color: #fff; color: #fff;
-webkit-appearance: none; -webkit-appearance: none;
text-align: center; text-align: center;
box-sizing: border-box; box-sizing: border-box;
outline: 0; outline: 0;
margin: 0; margin: 0;
padding: 10px 15px; padding: 10px 15px;
font-size: 14px; font-size: 14px;
border-radius: 4px; border-radius: 4px;
} }

View File

@@ -1,31 +1,31 @@
/** /**
* I think element-ui's default theme color is too light for long-term use. * I think element-ui's default theme color is too light for long-term use.
* So I modified the default color and you can modify it to your liking. * So I modified the default color and you can modify it to your liking.
**/ **/
/* theme color */ /* theme color */
$--color-primary: #1890ff; $--color-primary: #1890ff;
$--color-success: #13ce66; $--color-success: #13ce66;
$--color-warning: #ffba00; $--color-warning: #ffba00;
$--color-danger: #ff4949; $--color-danger: #ff4949;
// $--color-info: #1E1E1E; // $--color-info: #1E1E1E;
$--button-font-weight: 400; $--button-font-weight: 400;
// $--color-text-regular: #1f2d3d; // $--color-text-regular: #1f2d3d;
$--border-color-light: #dfe4ed; $--border-color-light: #dfe4ed;
$--border-color-lighter: #e6ebf5; $--border-color-lighter: #e6ebf5;
$--table-border: 1px solid #dfe6ec; $--table-border: 1px solid #dfe6ec;
/* icon font path, required */ /* icon font path, required */
$--font-path: '~element-ui/lib/theme-chalk/fonts'; $--font-path: '~element-ui/lib/theme-chalk/fonts';
@import "~element-ui/packages/theme-chalk/src/index"; @import "~element-ui/packages/theme-chalk/src/index";
// the :export directive is the magic sauce for webpack // the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass // https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export { :export {
theme: $--color-primary; theme: $--color-primary;
} }

View File

@@ -1,66 +1,66 @@
@mixin clearfix { @mixin clearfix {
&:after { &:after {
content: ""; content: "";
display: table; display: table;
clear: both; clear: both;
} }
} }
@mixin scrollBar { @mixin scrollBar {
&::-webkit-scrollbar-track-piece { &::-webkit-scrollbar-track-piece {
background: #d3dce6; background: #d3dce6;
} }
&::-webkit-scrollbar { &::-webkit-scrollbar {
width: 6px; width: 6px;
} }
&::-webkit-scrollbar-thumb { &::-webkit-scrollbar-thumb {
background: #99a9bf; background: #99a9bf;
border-radius: 20px; border-radius: 20px;
} }
} }
@mixin relative { @mixin relative {
position: relative; position: relative;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }
@mixin pct($pct) { @mixin pct($pct) {
width: #{$pct}; width: #{$pct};
position: relative; position: relative;
margin: 0 auto; margin: 0 auto;
} }
@mixin triangle($width, $height, $color, $direction) { @mixin triangle($width, $height, $color, $direction) {
$width: $width/2; $width: $width/2;
$color-border-style: $height solid $color; $color-border-style: $height solid $color;
$transparent-border-style: $width solid transparent; $transparent-border-style: $width solid transparent;
height: 0; height: 0;
width: 0; width: 0;
@if $direction==up { @if $direction==up {
border-bottom: $color-border-style; border-bottom: $color-border-style;
border-left: $transparent-border-style; border-left: $transparent-border-style;
border-right: $transparent-border-style; border-right: $transparent-border-style;
} }
@else if $direction==right { @else if $direction==right {
border-left: $color-border-style; border-left: $color-border-style;
border-top: $transparent-border-style; border-top: $transparent-border-style;
border-bottom: $transparent-border-style; border-bottom: $transparent-border-style;
} }
@else if $direction==down { @else if $direction==down {
border-top: $color-border-style; border-top: $color-border-style;
border-left: $transparent-border-style; border-left: $transparent-border-style;
border-right: $transparent-border-style; border-right: $transparent-border-style;
} }
@else if $direction==left { @else if $direction==left {
border-right: $color-border-style; border-right: $color-border-style;
border-top: $transparent-border-style; border-top: $transparent-border-style;
border-bottom: $transparent-border-style; border-bottom: $transparent-border-style;
} }
} }

View File

@@ -81,10 +81,6 @@
margin-top: 6vh !important; margin-top: 6vh !important;
} }
.el-dialog__body {
padding: 8px 20px !important;
}
.el-dialog__wrapper.scrollbar .el-dialog .el-dialog__body { .el-dialog__wrapper.scrollbar .el-dialog .el-dialog__body {
overflow: auto; overflow: auto;
overflow-x: hidden; overflow-x: hidden;
@@ -232,191 +228,6 @@
color: #FFFFFF; color: #FFFFFF;
} }
/** 详细卡片样式 */
.detail-drawer {
.el-drawer__header {
margin-bottom: 6px;
padding: 8px 12px 6px;
font-size: 15px;
color: #303133;
background: #f8f8f8;
}
.section-header {
font-size: 15px;
color: #6379bb;
border-bottom: 1px solid #ddd;
margin: 12px 0 16px 0;
padding-bottom: 8px;
}
.drawer-content {
padding: 0 20px 20px 20px;
.info-item {
display: flex;
align-items: flex-start;
padding: 8px 0;
min-height: 40px;
}
.info-label {
flex-shrink: 0;
width: 200px;
color: #606266;
font-size: 13px;
line-height: 1.6;
padding-top: 4px;
text-align: right;
margin-right: 14px;
}
.info-value {
flex: 1;
color: #303133;
font-size: 13px;
font-weight: 500;
line-height: 1.6;
word-break: break-all;
padding-top: 4px;
min-height: 1.6em;
&.plaintext {
border-bottom: 1px dashed #dde1e6;
}
}
}
}
.detail-wrap { padding: 0 4px; }
.detail-card {
border: 1px solid #ebeef5;
border-radius: 6px;
margin-bottom: 14px;
overflow: hidden;
}
.detail-card-title {
background: #f7f9fb;
padding: 8px 16px;
font-size: 13px;
font-weight: 600;
color: #333;
border-bottom: 1px solid #ebeef5;
}
.detail-card-title i { margin-right: 5px; color: #409EFF; }
.detail-row { padding: 0 8px; }
.detail-item {
display: flex;
align-items: flex-start;
padding: 10px 8px;
font-size: 13px;
border-bottom: 1px solid #f5f7fa;
}
.detail-item:last-child { border-bottom: none; }
.detail-label {
flex-shrink: 0;
width: 72px;
color: #909399;
margin-right: 12px;
}
.detail-value { color: #303133; flex: 1; word-break: break-all; }
.detail-location { color: #999; font-size: 12px; }
.method-tag {
display: inline-block;
padding: 1px 7px;
border-radius: 3px;
font-size: 11px;
font-weight: 700;
margin-right: 6px;
vertical-align: middle;
}
.mono { font-family: Consolas, 'SFMono-Regular', monospace; font-size: 12px; }
.code-body { padding: 14px; }
.code-wrap {
background: #f7f9fb;
border: 1px solid #e8ecf0;
border-radius: 4px;
overflow: hidden;
max-height: 260px;
position: relative;
}
.code-action {
position: absolute;
top: 8px;
right: 8px;
z-index: 10;
margin: 0;
padding: 0;
}
.code-action .el-button {
height: 24px;
font-size: 12px;
padding: 4px 8px;
background: rgba(255, 255, 255, 0.9);
border: 1px solid #dcdcdc;
}
.code-action .el-button:hover {
background: #ffffff;
border-color: #409EFF;
}
.code-pre {
margin: 0;
padding: 12px 14px;
font-size: 12px;
line-height: 1.6;
font-family: Consolas, 'SFMono-Regular', monospace;
color: #444;
white-space: pre-wrap;
word-break: break-all;
overflow: auto;
max-height: 240px;
display: block;
}
/* tree-sidebar content */
.tree-sidebar-manage-wrap {
display: flex;
gap: 0;
min-height: calc(100vh - 130px);
padding: 0 !important;
overflow: hidden;
}
.tree-sidebar-content {
flex: 1;
min-width: 0;
overflow: hidden;
background: #fff;
.content-inner {
padding: 12px 16px;
height: 100%;
overflow-y: auto;
}
}
/* error */
.error-title { color: #c0392b !important; }
.error-title i { color: #c0392b !important; }
.error-body { padding: 12px 16px; }
.error-msg {
background: #fff8f8;
border-left: 3px solid #e74c3c;
border-radius: 3px;
padding: 8px 12px;
color: #c0392b;
font-size: 12px;
line-height: 1.7;
word-break: break-all;
white-space: pre-wrap;
}
/* http method */
.method-GET { background: #e8f5e9; color: #27ae60; }
.method-POST { background: #e3f2fd; color: #1565c0; }
.method-PUT { background: #fff3e0; color: #e65100; }
.method-DELETE { background: #ffebee; color: #c62828; }
/* text color */ /* text color */
.text-navy { .text-navy {
color: #1ab394; color: #1ab394;
@@ -469,9 +280,6 @@
} }
/* 拖拽列样式 */ /* 拖拽列样式 */
.allowDrag { cursor: grab; }
.allowDrag:active { cursor: grabbing; }
.sortable-ghost { .sortable-ghost {
opacity: .8; opacity: .8;
color: #fff !important; color: #fff !important;
@@ -482,3 +290,8 @@
position: relative; position: relative;
float: right; float: right;
} }
/* 分割面板样式 */
.splitpanes.default-theme .splitpanes__pane {
background-color: #fff!important;
}

View File

@@ -1,321 +1,229 @@
#app { #app {
.main-container { .main-container {
height: 100%; height: 100%;
transition: margin-left .28s; transition: margin-left .28s;
margin-left: $base-sidebar-width; margin-left: $base-sidebar-width;
position: relative; position: relative;
} }
.sidebarHide { .sidebarHide {
margin-left: 0!important; margin-left: 0!important;
} }
.sidebar-container { .sidebar-container {
-webkit-transition: width .28s; -webkit-transition: width .28s;
transition: width 0.28s; transition: width 0.28s;
width: $base-sidebar-width !important; width: $base-sidebar-width !important;
background-color: $base-menu-background; background-color: $base-menu-background;
height: 100%; height: 100%;
position: fixed; position: fixed;
font-size: 0px; font-size: 0px;
top: 0; top: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
z-index: 1001; z-index: 1001;
overflow: hidden; overflow: hidden;
-webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35); -webkit-box-shadow: 2px 0 6px rgba(0,21,41,.35);
box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1); box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.1);
// reset element-ui css // reset element-ui css
.horizontal-collapse-transition { .horizontal-collapse-transition {
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out; transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
} }
.scrollbar-wrapper { .scrollbar-wrapper {
overflow-x: hidden !important; overflow-x: hidden !important;
} }
.el-scrollbar__bar.is-vertical { .el-scrollbar__bar.is-vertical {
right: 0px; right: 0px;
} }
.el-scrollbar { .el-scrollbar {
height: 100%; height: 100%;
} }
&.has-logo { &.has-logo {
.el-scrollbar { .el-scrollbar {
height: calc(100% - 50px); height: calc(100% - 50px);
} }
} }
.is-horizontal { .is-horizontal {
display: none; display: none;
} }
a { a {
display: inline-block; display: inline-block;
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
} }
.svg-icon { .svg-icon {
margin-right: 10px !important; margin-right: 10px !important;
} }
.el-menu { .el-menu {
border: none; border: none;
height: 100%; height: 100%;
width: 100% !important; width: 100% !important;
} }
.el-menu-item, .el-submenu__title { .el-menu-item, .el-submenu__title {
overflow: hidden !important; overflow: hidden !important;
text-overflow: ellipsis !important; text-overflow: ellipsis !important;
white-space: nowrap !important; white-space: nowrap !important;
height: 44px !important; }
line-height: 44px !important;
} // menu hover
.submenu-title-noDropdown,
// menu hover .el-submenu__title {
.submenu-title-noDropdown, &:hover {
.el-submenu__title { background-color: rgba(0, 0, 0, 0.06) !important;
&:hover { }
background-color: rgba(0, 0, 0, 0.06) !important; }
}
} & .theme-dark .is-active > .el-submenu__title {
color: $base-menu-color-active !important;
& .theme-dark .is-active > .el-submenu__title { }
color: $base-menu-color-active !important;
} & .nest-menu .el-submenu>.el-submenu__title,
& .el-submenu .el-menu-item {
& .nest-menu .el-submenu>.el-submenu__title, min-width: $base-sidebar-width !important;
& .el-submenu .el-menu-item {
min-width: $base-sidebar-width !important; &:hover {
background-color: rgba(0, 0, 0, 0.06) !important;
&:hover { }
background-color: rgba(0, 0, 0, 0.06) !important; }
}
} & .theme-dark .nest-menu .el-submenu>.el-submenu__title,
& .theme-dark .el-submenu .el-menu-item {
& .theme-dark .nest-menu .el-submenu>.el-submenu__title, background-color: $base-sub-menu-background !important;
& .theme-dark .el-submenu .el-menu-item {
background-color: $base-sub-menu-background !important; &:hover {
background-color: $base-sub-menu-hover !important;
&:hover { }
background-color: $base-sub-menu-hover !important; }
} }
}
.hideSidebar {
// theme-dark 深色主题 .sidebar-container {
&.theme-dark { width: 54px !important;
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.4); }
border-right: none;
.main-container {
.el-menu-item.is-active { margin-left: 54px;
position: relative; }
&::before { .el-menu:not(.el-menu--horizontal) {
content: ''; .submenu-title-noDropdown {
position: absolute; padding: 0 !important;
inset: 0; position: relative;
background-color: var(--current-color-dark-bg, rgba(64, 158, 255, 0.2));
border-right: 3px solid var(--current-color, #409eff); .el-tooltip {
pointer-events: none; padding: 0 !important;
z-index: 1;
} .svg-icon {
} margin-left: 20px;
}
.el-submenu.is-active > .el-submenu__title { }
color: var(--current-color, #409eff) !important; }
} }
.el-menu-item:not(.is-active), .el-submenu {
.submenu-title-noDropdown, overflow: hidden;
.el-submenu__title {
position: relative; &>.el-submenu__title {
padding: 0 !important;
&::before {
content: ''; .svg-icon {
position: absolute; margin-left: 20px;
inset: 0; }
background-color: transparent;
pointer-events: none; }
z-index: 1; }
transition: background-color 0.2s;
} .el-menu--collapse {
.el-submenu {
&:hover::before { &>.el-submenu__title {
background-color: var(--current-color-dark-bg, rgba(64, 158, 255, 0.2)); &>span {
} height: 0;
} width: 0;
} overflow: hidden;
visibility: hidden;
// theme-light 浅色主题 display: inline-block;
&.theme-light { }
border-right: 1px solid #e8eaf0; }
box-shadow: none; }
}
.el-menu-item, }
.el-submenu__title {
color: rgba(0, 0, 0, 0.65); .el-menu--collapse .el-menu .el-submenu {
} min-width: $base-sidebar-width !important;
}
.el-menu-item.is-active {
color: var(--current-color, #409eff) !important; // mobile responsive
position: relative; .mobile {
.main-container {
&::before { margin-left: 0px;
content: ''; }
position: absolute;
inset: 0; .sidebar-container {
background-color: var(--current-color-light, #ecf5ff); transition: transform .28s;
border-right: 3px solid var(--current-color, #409eff); width: $base-sidebar-width !important;
pointer-events: none; }
z-index: 1;
} &.hideSidebar {
} .sidebar-container {
pointer-events: none;
.el-submenu.is-active > .el-submenu__title { transition-duration: 0.3s;
color: var(--current-color, #409eff) !important; transform: translate3d(-$base-sidebar-width, 0, 0);
} }
}
.el-menu-item:not(.is-active):hover, }
.submenu-title-noDropdown:hover,
.el-submenu__title:hover { .withoutAnimation {
background-color: #f5f7fa !important;
color: rgba(0, 0, 0, 0.85) !important; .main-container,
} .sidebar-container {
transition: none;
.nest-menu .el-submenu > .el-submenu__title, }
.el-submenu .el-menu-item { }
background-color: #fafafa !important; }
&:hover { // when menu collapsed
background-color: #f0f5ff !important; .el-menu--vertical {
} &>.el-menu {
} .svg-icon {
} margin-right: 16px;
} }
}
.hideSidebar {
.sidebar-container { .nest-menu .el-submenu>.el-submenu__title,
width: 54px !important; .el-menu-item {
} &:hover {
// you can use $subMenuHover
.main-container { background-color: rgba(0, 0, 0, 0.06) !important;
margin-left: 54px; }
} }
.el-menu:not(.el-menu--horizontal) { // the scroll bar appears when the subMenu is too long
.submenu-title-noDropdown { >.el-menu--popup {
padding: 0 !important; max-height: 100vh;
position: relative; overflow-y: auto;
.el-tooltip { &::-webkit-scrollbar-track-piece {
padding: 0 !important; background: #d3dce6;
}
.svg-icon {
margin-left: 20px; &::-webkit-scrollbar {
} width: 6px;
} }
}
} &::-webkit-scrollbar-thumb {
background: #99a9bf;
.el-submenu { border-radius: 20px;
overflow: hidden; }
}
&>.el-submenu__title { }
padding: 0 !important;
.svg-icon {
margin-left: 20px;
}
}
}
.el-menu--collapse {
.el-submenu {
&>.el-submenu__title {
&>span {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
}
}
}
}
.el-menu--collapse .el-menu .el-submenu {
min-width: $base-sidebar-width !important;
}
// mobile responsive
.mobile {
.main-container {
margin-left: 0px;
}
.sidebar-container {
transition: transform .28s;
width: $base-sidebar-width !important;
}
&.hideSidebar {
.sidebar-container {
pointer-events: none;
transition-duration: 0.3s;
transform: translate3d(-$base-sidebar-width, 0, 0);
}
}
}
.withoutAnimation {
.main-container,
.sidebar-container {
transition: none;
}
}
}
// when menu collapsed
.el-menu--vertical {
&>.el-menu {
.svg-icon {
margin-right: 16px;
}
}
.nest-menu .el-submenu>.el-submenu__title,
.el-menu-item {
&:hover {
// you can use $subMenuHover
background-color: rgba(0, 0, 0, 0.06) !important;
}
}
// the scroll bar appears when the subMenu is too long
>.el-menu--popup {
max-height: 100vh;
overflow-y: auto;
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
}
}

View File

@@ -1,49 +1,49 @@
// global transition css // global transition css
/* fade */ /* fade */
.fade-enter-active, .fade-enter-active,
.fade-leave-active { .fade-leave-active {
transition: opacity 0.28s; transition: opacity 0.28s;
} }
.fade-enter, .fade-enter,
.fade-leave-active { .fade-leave-active {
opacity: 0; opacity: 0;
} }
/* fade-transform */ /* fade-transform */
.fade-transform--move, .fade-transform--move,
.fade-transform-leave-active, .fade-transform-leave-active,
.fade-transform-enter-active { .fade-transform-enter-active {
transition: all .5s; transition: all .5s;
} }
.fade-transform-enter { .fade-transform-enter {
opacity: 0; opacity: 0;
transform: translateX(-30px); transform: translateX(-30px);
} }
.fade-transform-leave-to { .fade-transform-leave-to {
opacity: 0; opacity: 0;
transform: translateX(30px); transform: translateX(30px);
} }
/* breadcrumb transition */ /* breadcrumb transition */
.breadcrumb-enter-active, .breadcrumb-enter-active,
.breadcrumb-leave-active { .breadcrumb-leave-active {
transition: all .5s; transition: all .5s;
} }
.breadcrumb-enter, .breadcrumb-enter,
.breadcrumb-leave-active { .breadcrumb-leave-active {
opacity: 0; opacity: 0;
transform: translateX(20px); transform: translateX(20px);
} }
.breadcrumb-move { .breadcrumb-move {
transition: all .5s; transition: all .5s;
} }
.breadcrumb-leave-active { .breadcrumb-leave-active {
position: absolute; position: absolute;
} }

View File

@@ -1,39 +1,54 @@
// base color // base color
$blue:#324157; $blue:#324157;
$light-blue:#3A71A8; $light-blue:#3A71A8;
$red:#C03639; $red:#C03639;
$pink: #E65D6E; $pink: #E65D6E;
$green: #30B08F; $green: #30B08F;
$tiffany: #4AB7BD; $tiffany: #4AB7BD;
$yellow:#FEC171; $yellow:#FEC171;
$panGreen: #30B08F; $panGreen: #30B08F;
// 默认菜单主题风格 // 默认菜单主题风格
$base-menu-color: #bfcbd9; $base-menu-color:#bfcbd9;
$base-menu-color-active: #ffffff; $base-menu-color-active:#f4f4f5;
$base-menu-background: #1a1f2e; $base-menu-background:#304156;
$base-logo-title-color: #ffffff; $base-logo-title-color: #ffffff;
$base-menu-light-color:rgba(0,0,0,.70); $base-menu-light-color:rgba(0,0,0,.70);
$base-menu-light-background:#ffffff; $base-menu-light-background:#ffffff;
$base-logo-light-title-color: #001529; $base-logo-light-title-color: #001529;
$base-sub-menu-background: #141824; $base-sub-menu-background:#1f2d3d;
$base-sub-menu-hover: rgba(255,255,255,.06); $base-sub-menu-hover:#001528;
$base-sidebar-width: 200px; // 自定义暗色菜单风格
/**
// the :export directive is the magic sauce for webpack $base-menu-color:hsla(0,0%,100%,.65);
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass $base-menu-color-active:#fff;
:export { $base-menu-background:#001529;
menuColor: $base-menu-color; $base-logo-title-color: #ffffff;
menuLightColor: $base-menu-light-color;
menuColorActive: $base-menu-color-active; $base-menu-light-color:rgba(0,0,0,.70);
menuBackground: $base-menu-background; $base-menu-light-background:#ffffff;
menuLightBackground: $base-menu-light-background; $base-logo-light-title-color: #001529;
subMenuBackground: $base-sub-menu-background;
subMenuHover: $base-sub-menu-hover; $base-sub-menu-background:#000c17;
sideBarWidth: $base-sidebar-width; $base-sub-menu-hover:#001528;
logoTitleColor: $base-logo-title-color; */
logoLightTitleColor: $base-logo-light-title-color
} $base-sidebar-width: 200px;
// the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export {
menuColor: $base-menu-color;
menuLightColor: $base-menu-light-color;
menuColorActive: $base-menu-color-active;
menuBackground: $base-menu-background;
menuLightBackground: $base-menu-light-background;
subMenuBackground: $base-sub-menu-background;
subMenuHover: $base-sub-menu-hover;
sideBarWidth: $base-sidebar-width;
logoTitleColor: $base-logo-title-color;
logoLightTitleColor: $base-logo-light-title-color
}

View File

@@ -1,116 +1,116 @@
<template> <template>
<el-form size="small"> <el-form size="small">
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="1"> <el-radio v-model='radioValue' :label="1">
分钟允许的通配符[, - * /] 分钟允许的通配符[, - * /]
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="2"> <el-radio v-model='radioValue' :label="2">
周期从 周期从
<el-input-number v-model='cycle01' :min="0" :max="58" /> - <el-input-number v-model='cycle01' :min="0" :max="58" /> -
<el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 1" :max="59" /> 分钟 <el-input-number v-model='cycle02' :min="cycle01 ? cycle01 + 1 : 1" :max="59" /> 分钟
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="3"> <el-radio v-model='radioValue' :label="3">
<el-input-number v-model='average01' :min="0" :max="58" /> 分钟开始 <el-input-number v-model='average01' :min="0" :max="58" /> 分钟开始
<el-input-number v-model='average02' :min="1" :max="59 - average01 || 0" /> 分钟执行一次 <el-input-number v-model='average02' :min="1" :max="59 - average01 || 0" /> 分钟执行一次
</el-radio> </el-radio>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-radio v-model='radioValue' :label="4"> <el-radio v-model='radioValue' :label="4">
指定 指定
<el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%"> <el-select clearable v-model="checkboxList" placeholder="可多选" multiple style="width:100%">
<el-option v-for="item in 60" :key="item" :value="item-1">{{item-1}}</el-option> <el-option v-for="item in 60" :key="item" :value="item-1">{{item-1}}</el-option>
</el-select> </el-select>
</el-radio> </el-radio>
</el-form-item> </el-form-item>
</el-form> </el-form>
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
radioValue: 1, radioValue: 1,
cycle01: 1, cycle01: 1,
cycle02: 2, cycle02: 2,
average01: 0, average01: 0,
average02: 1, average02: 1,
checkboxList: [], checkboxList: [],
checkNum: this.$options.propsData.check checkNum: this.$options.propsData.check
} }
}, },
name: 'crontab-min', name: 'crontab-min',
props: ['check', 'cron'], props: ['check', 'cron'],
methods: { methods: {
// 单选按钮值变化时 // 单选按钮值变化时
radioChange() { radioChange() {
switch (this.radioValue) { switch (this.radioValue) {
case 1: case 1:
this.$emit('update', 'min', '*', 'min') this.$emit('update', 'min', '*', 'min')
break break
case 2: case 2:
this.$emit('update', 'min', this.cycleTotal, 'min') this.$emit('update', 'min', this.cycleTotal, 'min')
break break
case 3: case 3:
this.$emit('update', 'min', this.averageTotal, 'min') this.$emit('update', 'min', this.averageTotal, 'min')
break break
case 4: case 4:
this.$emit('update', 'min', this.checkboxString, 'min') this.$emit('update', 'min', this.checkboxString, 'min')
break break
} }
}, },
// 周期两个值变化时 // 周期两个值变化时
cycleChange() { cycleChange() {
if (this.radioValue == '2') { if (this.radioValue == '2') {
this.$emit('update', 'min', this.cycleTotal, 'min') this.$emit('update', 'min', this.cycleTotal, 'min')
} }
}, },
// 平均两个值变化时 // 平均两个值变化时
averageChange() { averageChange() {
if (this.radioValue == '3') { if (this.radioValue == '3') {
this.$emit('update', 'min', this.averageTotal, 'min') this.$emit('update', 'min', this.averageTotal, 'min')
} }
}, },
// checkbox值变化时 // checkbox值变化时
checkboxChange() { checkboxChange() {
if (this.radioValue == '4') { if (this.radioValue == '4') {
this.$emit('update', 'min', this.checkboxString, 'min') this.$emit('update', 'min', this.checkboxString, 'min')
} }
}, },
}, },
watch: { watch: {
'radioValue': 'radioChange', 'radioValue': 'radioChange',
'cycleTotal': 'cycleChange', 'cycleTotal': 'cycleChange',
'averageTotal': 'averageChange', 'averageTotal': 'averageChange',
'checkboxString': 'checkboxChange', 'checkboxString': 'checkboxChange',
}, },
computed: { computed: {
// 计算两个周期值 // 计算两个周期值
cycleTotal: function () { cycleTotal: function () {
const cycle01 = this.checkNum(this.cycle01, 0, 58) const cycle01 = this.checkNum(this.cycle01, 0, 58)
const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 1, 59) const cycle02 = this.checkNum(this.cycle02, cycle01 ? cycle01 + 1 : 1, 59)
return cycle01 + '-' + cycle02 return cycle01 + '-' + cycle02
}, },
// 计算平均用到的值 // 计算平均用到的值
averageTotal: function () { averageTotal: function () {
const average01 = this.checkNum(this.average01, 0, 58) const average01 = this.checkNum(this.average01, 0, 58)
const average02 = this.checkNum(this.average02, 1, 59 - average01 || 0) const average02 = this.checkNum(this.average02, 1, 59 - average01 || 0)
return average01 + '/' + average02 return average01 + '/' + average02
}, },
// 计算勾选的checkbox值合集 // 计算勾选的checkbox值合集
checkboxString: function () { checkboxString: function () {
let str = this.checkboxList.join() let str = this.checkboxList.join()
return str == '' ? '*' : str return str == '' ? '*' : str
} }
} }
} }
</script> </script>

View File

@@ -1,49 +1,49 @@
import Vue from 'vue' import Vue from 'vue'
import store from '@/store' import store from '@/store'
import DataDict from '@/utils/dict' import DataDict from '@/utils/dict'
import { getDicts as getDicts } from '@/api/system/dict/data' import { getDicts as getDicts } from '@/api/system/dict/data'
function searchDictByKey(dict, key) { function searchDictByKey(dict, key) {
if (key == null && key == "") { if (key == null && key == "") {
return null return null
} }
try { try {
for (let i = 0; i < dict.length; i++) { for (let i = 0; i < dict.length; i++) {
if (dict[i].key == key) { if (dict[i].key == key) {
return dict[i].value return dict[i].value
} }
} }
} catch (e) { } catch (e) {
return null return null
} }
} }
function install() { function install() {
Vue.use(DataDict, { Vue.use(DataDict, {
metas: { metas: {
'*': { '*': {
labelField: 'dictLabel', labelField: 'dictLabel',
valueField: 'dictValue', valueField: 'dictValue',
request(dictMeta) { request(dictMeta) {
const storeDict = searchDictByKey(store.getters.dict, dictMeta.type) const storeDict = searchDictByKey(store.getters.dict, dictMeta.type)
if (storeDict) { if (storeDict) {
return new Promise(resolve => { resolve(storeDict) }) return new Promise(resolve => { resolve(storeDict) })
} else { } else {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
getDicts(dictMeta.type).then(res => { getDicts(dictMeta.type).then(res => {
store.dispatch('dict/setDict', { key: dictMeta.type, value: res.data }) store.dispatch('dict/setDict', { key: dictMeta.type, value: res.data })
resolve(res.data) resolve(res.data)
}).catch(error => { }).catch(error => {
reject(error) reject(error)
}) })
}) })
} }
}, },
}, },
}, },
}) })
} }
export default { export default {
install, install,
} }

View File

@@ -1,126 +0,0 @@
<template>
<el-dialog :title="title" :visible.sync="visible" :width="width" append-to-body @close="handleClose">
<el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="headers" :action="uploadUrl" :disabled="isUploading" :on-progress="handleProgress" :on-success="handleSuccess" :auto-upload="false" drag>
<i class="el-icon-upload"></i>
<div class="el-upload__text">将文件拖到此处<em>点击上传</em></div>
<div class="el-upload__tip text-center" slot="tip">
<div class="el-upload__tip" slot="tip">
<el-checkbox v-model="updateSupport"> {{ updateSupportLabel }} </el-checkbox>
</div>
<span>仅允许导入xlsxlsx格式文件</span>
<el-link v-if="templateUrl" type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="handleDownloadTemplate">下载模板</el-link>
</div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="handleSubmit"> </el-button>
<el-button @click="visible = false"> </el-button>
</div>
</el-dialog>
</template>
<script>
import { getToken } from '@/utils/auth'
export default {
props: {
// 对话框标题
title: {
type: String,
default: '数据导入'
},
// 对话框宽度
width: {
type: String,
default: '400px'
},
// 上传接口地址(必传)
action: {
type: String,
required: true
},
// 模板下载接口地址,不传则不显示下载模板链接
templateAction: {
type: String,
default: ''
},
// 模板文件名
templateFileName: {
type: String,
default: 'template'
},
// 覆盖更新勾选框的说明文字
updateSupportLabel: {
type: String,
default: '是否更新已经存在的数据'
}
},
data() {
return {
visible: false,
isUploading: false,
updateSupport: false,
headers: { Authorization: 'Bearer ' + getToken() }
}
},
computed: {
uploadUrl() {
return process.env.VUE_APP_BASE_API + this.action + '?updateSupport=' + (this.updateSupport ? 1 : 0)
},
templateUrl() {
return !!this.templateAction
}
},
methods: {
// 打开对话框(供父组件通过 ref 调用)
open() {
this.updateSupport = false
this.isUploading = false
this.visible = true
this.$nextTick(() => {
if (this.$refs.uploadRef) {
this.$refs.uploadRef.clearFiles()
}
})
},
// 关闭时清理
handleClose() {
this.isUploading = false
if (this.$refs.uploadRef) {
this.$refs.uploadRef.clearFiles()
}
},
// 下载模板
handleDownloadTemplate() {
this.download(this.templateAction, {}, `${this.templateFileName}_${new Date().getTime()}.xlsx`)
},
// 上传进度
handleProgress() {
this.isUploading = true
},
// 上传成功
handleSuccess(response) {
this.visible = false
this.isUploading = false
if (this.$refs.uploadRef) {
this.$refs.uploadRef.clearFiles()
}
this.$alert("<div style='overflow:auto;overflow-x:hidden;max-height:70vh;padding:10px 20px 0;'>" + response.msg + '</div>', '导入结果', { dangerouslyUseHTMLString: true })
this.$emit('success')
},
// 提交上传
handleSubmit() {
const files = this.$refs.uploadRef.uploadFiles
if (!files || files.length === 0) {
this.$modal.msgError('请选择要上传的文件。')
return
}
const name = files[0].name.toLowerCase()
if (!name.endsWith('.xls') && !name.endsWith('.xlsx')) {
this.$modal.msgError('请选择后缀为 "xls" 或 "xlsx" 的文件。')
return
}
this.$refs.uploadRef.submit()
}
}
}
</script>

View File

@@ -1,44 +1,44 @@
<template> <template>
<div style="padding: 0 15px;" @click="toggleClick"> <div style="padding: 0 15px;" @click="toggleClick">
<svg <svg
:class="{'is-active':isActive}" :class="{'is-active':isActive}"
class="hamburger" class="hamburger"
viewBox="0 0 1024 1024" viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="64" width="64"
height="64" height="64"
> >
<path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" /> <path d="M408 442h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8zm-8 204c0 4.4 3.6 8 8 8h480c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8H408c-4.4 0-8 3.6-8 8v56zm504-486H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zm0 632H120c-4.4 0-8 3.6-8 8v56c0 4.4 3.6 8 8 8h784c4.4 0 8-3.6 8-8v-56c0-4.4-3.6-8-8-8zM142.4 642.1L298.7 519a8.84 8.84 0 0 0 0-13.9L142.4 381.9c-5.8-4.6-14.4-.5-14.4 6.9v246.3a8.9 8.9 0 0 0 14.4 7z" />
</svg> </svg>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'Hamburger', name: 'Hamburger',
props: { props: {
isActive: { isActive: {
type: Boolean, type: Boolean,
default: false default: false
} }
}, },
methods: { methods: {
toggleClick() { toggleClick() {
this.$emit('toggleClick') this.$emit('toggleClick')
} }
} }
} }
</script> </script>
<style scoped> <style scoped>
.hamburger { .hamburger {
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
width: 20px; width: 20px;
height: 20px; height: 20px;
} }
.hamburger.is-active { .hamburger.is-active {
transform: rotate(180deg); transform: rotate(180deg);
} }
</style> </style>

View File

@@ -1,397 +1,264 @@
<template> <template>
<div class="header-search"> <div class="header-search">
<svg-icon class-name="search-icon" icon-class="search" @click.stop="click" /> <svg-icon class-name="search-icon" icon-class="search" @click.stop="click" />
<el-dialog <el-dialog
:visible.sync="show" :visible.sync="show"
width="600px" width="600px"
@close="close" @close="close"
@opened="onDialogOpened" :show-close="false"
:show-close="false" append-to-body
append-to-body >
> <el-input
<el-input v-model="search"
v-model="search" ref="headerSearchSelectRef"
ref="headerSearchSelectRef" size="large"
size="large" @input="querySearch"
@input="querySearch" prefix-icon="el-icon-search"
prefix-icon="el-icon-search" placeholder="菜单搜索支持标题、URL模糊查询"
placeholder="菜单搜索支持标题、URL模糊查询" clearable
clearable @keyup.enter.native="selectActiveResult"
@keyup.enter.native="selectActiveResult" @keydown.up.native="navigateResult('up')"
@keydown.up.native="navigateResult('up')" @keydown.down.native="navigateResult('down')"
@keydown.down.native="navigateResult('down')" >
> </el-input>
</el-input> <el-scrollbar wrap-class="right-scrollbar-wrapper">
<div class="result-wrap">
<div class="result-count" v-if="search && options.length > 0"> <div class="search-item" v-for="(item, index) in options" :key="item.path" :style="activeStyle(index)" @mouseenter="activeIndex = index" @mouseleave="activeIndex = -1">
找到 <strong>{{ options.length }}</strong> 个结果 <div class="left">
</div> <svg-icon class="menu-icon" :icon-class="item.icon" />
</div>
<el-scrollbar wrap-class="right-scrollbar-wrapper"> <div class="search-info" @click="change(item)">
<div class="result-wrap"> <div class="menu-title">
<template v-if="options.length > 0"> {{ item.title.join(" / ") }}
<div </div>
class="search-item" <div class="menu-path">
v-for="(item, index) in options" {{ item.path }}
:key="item.path" </div>
:class="{ 'is-active': index === activeIndex }" </div>
:style="activeStyle(index)" <svg-icon icon-class="enter" v-show="index === activeIndex"/>
@mouseenter="activeIndex = index" </div>
@mouseleave="activeIndex = -1" </div>
> </el-scrollbar>
<div class="left"> </el-dialog>
<svg-icon class="menu-icon" :icon-class="item.icon" /> </div>
</div> </template>
<div class="search-info" @click="change(item)">
<div class="menu-title" v-html="highlightText(item.title.join(' / '))"></div> <script>
<div class="menu-path" v-html="highlightText(item.path)"></div> import Fuse from 'fuse.js/dist/fuse.min.js'
</div> import path from 'path'
<svg-icon icon-class="enter" v-show="index === activeIndex" /> import { isHttp } from '@/utils/validate'
</div>
</template> export default {
name: 'HeaderSearch',
<div class="empty-state" v-else-if="search && options.length === 0"> data() {
<i class="el-icon-search empty-icon"></i> return {
<p class="empty-text">未找到 "<strong>{{ search }}</strong>" 相关菜单</p> search: '',
<p class="empty-tip">试试其他关键词或路径</p> options: [],
</div> searchPool: [],
activeIndex: -1,
</div> show: false,
</el-scrollbar> fuse: undefined
}
<div class="search-footer"> },
<span class="shortcut-item"> computed: {
<kbd></kbd><kbd></kbd> 切换 theme() {
</span> return this.$store.state.settings.theme
<span class="shortcut-item"> },
<kbd></kbd> 选择 routes() {
</span> return this.$store.getters.defaultRoutes
<span class="shortcut-item"> }
<kbd>Esc</kbd> 关闭 },
</span> watch: {
</div> routes() {
</el-dialog> this.searchPool = this.generateRoutes(this.routes)
</div> },
</template> searchPool(list) {
this.initFuse(list)
<script> }
import Fuse from 'fuse.js/dist/fuse.min.js' },
import path from 'path' mounted() {
import { isHttp } from '@/utils/validate' this.searchPool = this.generateRoutes(this.routes)
},
export default { methods: {
name: 'HeaderSearch', click() {
data() { this.show = !this.show
return { if (this.show) {
search: '', this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.focus()
options: [], this.options = this.searchPool
searchPool: [], }
activeIndex: -1, },
show: false, close() {
fuse: undefined this.$refs.headerSearchSelect && this.$refs.headerSearchSelect.blur()
} this.search = ''
}, this.options = []
computed: { this.show = false
theme() { this.activeIndex = -1
return this.$store.state.settings.theme },
}, change(val) {
routes() { const path = val.path
return this.$store.getters.defaultRoutes const query = val.query
} if(isHttp(val.path)) {
}, // http(s):// 路径新窗口打开
watch: { const pindex = path.indexOf("http")
routes() { window.open(path.substr(pindex, path.length), "_blank")
this.searchPool = this.generateRoutes(this.routes) } else {
}, if (query) {
searchPool(list) { this.$router.push({ path: path, query: JSON.parse(query) })
this.initFuse(list) } else {
} this.$router.push(path)
}, }
mounted() { }
this.searchPool = this.generateRoutes(this.routes) this.search = ''
}, this.options = []
methods: { this.$nextTick(() => {
click() { this.show = false
this.show = !this.show })
if (this.show) { },
this.options = this.searchPool initFuse(list) {
} this.fuse = new Fuse(list, {
}, shouldSort: true,
onDialogOpened() { threshold: 0.4,
this.$nextTick(() => { location: 0,
this.$refs.headerSearchSelectRef && this.$refs.headerSearchSelectRef.focus() distance: 100,
}) minMatchCharLength: 1,
}, keys: [{
close() { name: 'title',
this.$refs.headerSearchSelectRef && this.$refs.headerSearchSelectRef.blur() weight: 0.7
this.search = '' }, {
this.options = this.searchPool name: 'path',
this.show = false weight: 0.3
this.activeIndex = -1 }]
}, })
change(val) { },
const p = val.path // Filter out the routes that can be displayed in the sidebar
const query = val.query // And generate the internationalized title
if (isHttp(val.path)) { generateRoutes(routes, basePath = '/', prefixTitle = []) {
// http(s):// 路径新窗口打开 let res = []
const pindex = p.indexOf('http')
window.open(p.substr(pindex, p.length), '_blank') for (const router of routes) {
} else { // skip hidden router
if (query) { if (router.hidden) { continue }
this.$router.push({ path: p, query: JSON.parse(query) })
} else { const data = {
this.$router.push(p) path: !isHttp(router.path) ? path.resolve(basePath, router.path) : router.path,
} title: [...prefixTitle],
} icon: ''
this.search = '' }
this.options = this.searchPool
this.$nextTick(() => { if (router.meta && router.meta.title) {
this.show = false data.title = [...data.title, router.meta.title]
}) data.icon = router.meta.icon
},
initFuse(list) { if (router.redirect !== 'noRedirect') {
this.fuse = new Fuse(list, { // only push the routes with title
shouldSort: true, // special case: need to exclude parent router without redirect
threshold: 0.2, res.push(data)
minMatchCharLength: 1, }
keys: [{ }
name: 'title',
weight: 0.7 if (router.query) {
}, { data.query = router.query
name: 'path', }
weight: 0.3
}] // recursive child routes
}) if (router.children) {
}, const tempRoutes = this.generateRoutes(router.children, data.path, data.title)
generateRoutes(routes, basePath = '/', prefixTitle = []) { if (tempRoutes.length >= 1) {
let res = [] res = [...res, ...tempRoutes]
for (const router of routes) { }
if (router.hidden) { continue } }
const data = { }
path: !isHttp(router.path) ? path.resolve(basePath, router.path) : router.path, return res
title: [...prefixTitle], },
icon: '' querySearch(query) {
} this.activeIndex = -1
if (router.meta && router.meta.title) { if (query !== '') {
data.title = [...data.title, router.meta.title] this.options = this.fuse.search(query).map((item) => item.item) ?? this.searchPool
data.icon = router.meta.icon } else {
if (router.redirect !== 'noRedirect') { this.options = this.searchPool
res.push(data) }
} },
} activeStyle(index) {
if (router.query) { if (index !== this.activeIndex) return {}
data.query = router.query return {
} "background-color": this.theme,
if (router.children) { "color": "#fff"
const tempRoutes = this.generateRoutes(router.children, data.path, data.title) }
if (tempRoutes.length >= 1) { },
res = [...res, ...tempRoutes] navigateResult(direction) {
} if (direction === "up") {
} this.activeIndex = this.activeIndex <= 0 ? this.options.length - 1 : this.activeIndex - 1
} } else if (direction === "down") {
return res this.activeIndex = this.activeIndex >= this.options.length - 1 ? 0 : this.activeIndex + 1
}, }
querySearch(query) { },
this.activeIndex = -1 selectActiveResult() {
if (query !== '') { if (this.options.length > 0 && this.activeIndex >= 0) {
const q = query.toLowerCase() this.change(this.options[this.activeIndex])
const pathMatches = this.searchPool.filter(item => }
item.path.toLowerCase().includes(q) }
) }
const fuseMatches = this.fuse.search(query).map(item => item.item) }
const merged = [...pathMatches] </script>
fuseMatches.forEach(item => {
if (!merged.find(m => m.path === item.path)) { <style lang='scss' scoped>
merged.push(item) ::v-deep {
} .el-dialog__header {
}) padding: 0 !important;
this.options = merged }
} else { }
this.options = this.searchPool
} .header-search {
}, .search-icon {
activeStyle(index) { cursor: pointer;
if (index !== this.activeIndex) return {} font-size: 18px;
return { vertical-align: middle;
'background-color': this.theme, }
'color': '#fff' }
}
}, .result-wrap {
navigateResult(direction) { height: 280px;
if (direction === 'up') { margin: 6px 0;
this.activeIndex = this.activeIndex <= 0 ? this.options.length - 1 : this.activeIndex - 1
} else if (direction === 'down') { .search-item {
this.activeIndex = this.activeIndex >= this.options.length - 1 ? 0 : this.activeIndex + 1 display: flex;
} height: 48px;
}, align-items: center;
selectActiveResult() { padding-right: 10px;
if (this.options.length > 0 && this.activeIndex >= 0) {
this.change(this.options[this.activeIndex]) .left {
} width: 60px;
}, text-align: center;
highlightText(text) {
if (!text) return '' .menu-icon {
if (!this.search) return text width: 18px;
const keyword = this.escapeRegExp(this.search) height: 18px;
const reg = new RegExp(`(${keyword})`, 'gi') }
return text.replace(reg, '<span class="highlight">$1</span>') }
},
escapeRegExp(str) { .search-info {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') padding-left: 5px;
} margin-top: 10px;
} width: 100%;
} display: flex;
</script> flex-direction: column;
justify-content: flex-start;
<style lang='scss' scoped> flex: 1;
::v-deep {
.el-dialog__header { .menu-title,
padding: 6px !important; .menu-path {
} height: 20px;
}
.highlight { .menu-path {
color: red; color: #ccc;
font-weight: 600; font-size: 10px;
} }
}
.is-active .highlight { }
color: rgba(255, 255, 255, 0.9);
font-weight: 600; .search-item:hover {
} cursor: pointer;
} }
}
.header-search { </style>
.search-icon {
cursor: pointer;
font-size: 18px;
vertical-align: middle;
}
}
.result-count {
padding: 6px 16px 0;
font-size: 12px;
color: #aaa;
strong {
color: red;
font-weight: 600;
}
}
.result-wrap {
height: 280px;
margin: 4px 0;
.search-item {
display: flex;
height: 48px;
align-items: center;
padding-right: 10px;
border-radius: 4px;
transition: background 0.15s;
.left {
width: 60px;
text-align: center;
flex-shrink: 0;
.menu-icon {
width: 18px;
height: 18px;
}
}
.search-info {
padding-left: 5px;
margin-top: 10px;
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-start;
flex: 1;
overflow: hidden;
.menu-title,
.menu-path {
height: 20px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.menu-path {
color: #ccc;
font-size: 10px;
}
}
}
.search-item:hover {
cursor: pointer;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
.empty-icon {
font-size: 42px;
color: #e0e0e0;
margin-bottom: 14px;
}
.empty-text {
font-size: 14px;
color: #999;
margin: 0 0 6px;
strong {
color: #666;
}
}
.empty-tip {
font-size: 12px;
color: #bbb;
margin: 0;
}
}
}
.search-footer {
display: flex;
align-items: center;
gap: 28px;
padding: 10px 20px;
border-top: 1px solid #f0f0f0;
color: #999;
font-size: 12px;
.shortcut-item {
display: flex;
align-items: center;
gap: 5px;
}
kbd {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 20px;
height: 20px;
padding: 0 5px;
border: 1px solid #ddd;
border-radius: 4px;
background: #f7f7f7;
color: #555;
font-size: 11px;
font-family: inherit;
line-height: 1;
box-shadow: 0 1px 0 #ccc;
}
}
</style>

View File

@@ -1,11 +1,11 @@
const req = require.context('../../assets/icons/svg', false, /\.svg$/) const req = require.context('../../assets/icons/svg', false, /\.svg$/)
const requireAll = requireContext => requireContext.keys() const requireAll = requireContext => requireContext.keys()
const re = /\.\/(.*)\.svg/ const re = /\.\/(.*)\.svg/
const icons = requireAll(req).map(i => { const icons = requireAll(req).map(i => {
return i.match(re)[1] return i.match(re)[1]
}) })
export default icons export default icons

View File

@@ -1,272 +1,272 @@
<template> <template>
<div class="component-upload-image"> <div class="component-upload-image">
<el-upload <el-upload
multiple multiple
:disabled="disabled" :disabled="disabled"
:action="uploadImgUrl" :action="uploadImgUrl"
list-type="picture-card" list-type="picture-card"
:on-success="handleUploadSuccess" :on-success="handleUploadSuccess"
:before-upload="handleBeforeUpload" :before-upload="handleBeforeUpload"
:data="data" :data="data"
:limit="limit" :limit="limit"
:on-error="handleUploadError" :on-error="handleUploadError"
:on-exceed="handleExceed" :on-exceed="handleExceed"
ref="imageUpload" ref="imageUpload"
:on-remove="handleDelete" :on-remove="handleDelete"
:show-file-list="true" :show-file-list="true"
:headers="headers" :headers="headers"
:file-list="fileList" :file-list="fileList"
:on-preview="handlePictureCardPreview" :on-preview="handlePictureCardPreview"
:class="{hide: this.fileList.length >= this.limit}" :class="{hide: this.fileList.length >= this.limit}"
> >
<i class="el-icon-plus"></i> <i class="el-icon-plus"></i>
</el-upload> </el-upload>
<!-- 上传提示 --> <!-- 上传提示 -->
<div class="el-upload__tip" slot="tip" v-if="showTip && !disabled"> <div class="el-upload__tip" slot="tip" v-if="showTip && !disabled">
请上传 请上传
<template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template> <template v-if="fileSize"> 大小不超过 <b style="color: #f56c6c">{{ fileSize }}MB</b> </template>
<template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template> <template v-if="fileType"> 格式为 <b style="color: #f56c6c">{{ fileType.join("/") }}</b> </template>
的文件 的文件
</div> </div>
<el-dialog <el-dialog
:visible.sync="dialogVisible" :visible.sync="dialogVisible"
title="预览" title="预览"
width="800" width="800"
append-to-body append-to-body
> >
<img <img
:src="dialogImageUrl" :src="dialogImageUrl"
style="display: block; max-width: 100%; margin: 0 auto" style="display: block; max-width: 100%; margin: 0 auto"
/> />
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script> <script>
import { getToken } from "@/utils/auth" import { getToken } from "@/utils/auth"
import { isExternal } from "@/utils/validate" import { isExternal } from "@/utils/validate"
import Sortable from 'sortablejs' import Sortable from 'sortablejs'
export default { export default {
props: { props: {
value: [String, Object, Array], value: [String, Object, Array],
// 上传接口地址 // 上传接口地址
action: { action: {
type: String, type: String,
default: "/common/upload" default: "/common/upload"
}, },
// 上传携带的参数 // 上传携带的参数
data: { data: {
type: Object type: Object
}, },
// 图片数量限制 // 图片数量限制
limit: { limit: {
type: Number, type: Number,
default: 5 default: 5
}, },
// 大小限制(MB) // 大小限制(MB)
fileSize: { fileSize: {
type: Number, type: Number,
default: 5 default: 5
}, },
// 文件类型, 例如['png', 'jpg', 'jpeg'] // 文件类型, 例如['png', 'jpg', 'jpeg']
fileType: { fileType: {
type: Array, type: Array,
default: () => ["png", "jpg", "jpeg"] default: () => ["png", "jpg", "jpeg"]
}, },
// 是否显示提示 // 是否显示提示
isShowTip: { isShowTip: {
type: Boolean, type: Boolean,
default: true default: true
}, },
// 禁用组件(仅查看图片) // 禁用组件(仅查看图片)
disabled: { disabled: {
type: Boolean, type: Boolean,
default: false default: false
}, },
// 拖动排序 // 拖动排序
drag: { drag: {
type: Boolean, type: Boolean,
default: true default: true
} }
}, },
data() { data() {
return { return {
number: 0, number: 0,
uploadList: [], uploadList: [],
dialogImageUrl: "", dialogImageUrl: "",
dialogVisible: false, dialogVisible: false,
hideUpload: false, hideUpload: false,
baseUrl: process.env.VUE_APP_BASE_API, baseUrl: process.env.VUE_APP_BASE_API,
uploadImgUrl: process.env.VUE_APP_BASE_API + this.action, // 上传的图片服务器地址 uploadImgUrl: process.env.VUE_APP_BASE_API + this.action, // 上传的图片服务器地址
headers: { headers: {
Authorization: "Bearer " + getToken(), Authorization: "Bearer " + getToken(),
}, },
fileList: [] fileList: []
} }
}, },
mounted() { mounted() {
if (this.drag && !this.disabled) { if (this.drag && !this.disabled) {
this.$nextTick(() => { this.$nextTick(() => {
const element = this.$refs.imageUpload?.$el?.querySelector('.el-upload-list') const element = this.$refs.imageUpload?.$el?.querySelector('.el-upload-list')
Sortable.create(element, { Sortable.create(element, {
onEnd: (evt) => { onEnd: (evt) => {
const movedItem = this.fileList.splice(evt.oldIndex, 1)[0] const movedItem = this.fileList.splice(evt.oldIndex, 1)[0]
this.fileList.splice(evt.newIndex, 0, movedItem) this.fileList.splice(evt.newIndex, 0, movedItem)
this.$emit("input", this.listToString(this.fileList)) this.$emit("input", this.listToString(this.fileList))
} }
}) })
}) })
} }
}, },
watch: { watch: {
value: { value: {
handler(val) { handler(val) {
if (val) { if (val) {
// 首先将值转为数组 // 首先将值转为数组
const list = Array.isArray(val) ? val : this.value.split(',') const list = Array.isArray(val) ? val : this.value.split(',')
// 然后将数组转为对象数组 // 然后将数组转为对象数组
this.fileList = list.map(item => { this.fileList = list.map(item => {
if (typeof item === "string") { if (typeof item === "string") {
if (item.indexOf(this.baseUrl) === -1 && !isExternal(item)) { if (item.indexOf(this.baseUrl) === -1 && !isExternal(item)) {
item = { name: this.baseUrl + item, url: this.baseUrl + item } item = { name: this.baseUrl + item, url: this.baseUrl + item }
} else { } else {
item = { name: item, url: item } item = { name: item, url: item }
} }
} }
return item return item
}) })
} else { } else {
this.fileList = [] this.fileList = []
return [] return []
} }
}, },
deep: true, deep: true,
immediate: true immediate: true
} }
}, },
computed: { computed: {
// 是否显示提示 // 是否显示提示
showTip() { showTip() {
return this.isShowTip && (this.fileType || this.fileSize) return this.isShowTip && (this.fileType || this.fileSize)
}, },
}, },
methods: { methods: {
// 上传前loading加载 // 上传前loading加载
handleBeforeUpload(file) { handleBeforeUpload(file) {
let isImg = false let isImg = false
if (this.fileType.length) { if (this.fileType.length) {
let fileExtension = "" let fileExtension = ""
if (file.name.lastIndexOf(".") > -1) { if (file.name.lastIndexOf(".") > -1) {
fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1) fileExtension = file.name.slice(file.name.lastIndexOf(".") + 1)
} }
isImg = this.fileType.some(type => { isImg = this.fileType.some(type => {
if (file.type.indexOf(type) > -1) return true if (file.type.indexOf(type) > -1) return true
if (fileExtension && fileExtension.indexOf(type) > -1) return true if (fileExtension && fileExtension.indexOf(type) > -1) return true
return false return false
}) })
} else { } else {
isImg = file.type.indexOf("image") > -1 isImg = file.type.indexOf("image") > -1
} }
if (!isImg) { if (!isImg) {
this.$modal.msgError(`文件格式不正确,请上传${this.fileType.join("/")}图片格式文件!`) this.$modal.msgError(`文件格式不正确,请上传${this.fileType.join("/")}图片格式文件!`)
return false return false
} }
if (file.name.includes(',')) { if (file.name.includes(',')) {
this.$modal.msgError('文件名不正确,不能包含英文逗号!') this.$modal.msgError('文件名不正确,不能包含英文逗号!')
return false return false
} }
if (this.fileSize) { if (this.fileSize) {
const isLt = file.size / 1024 / 1024 < this.fileSize const isLt = file.size / 1024 / 1024 < this.fileSize
if (!isLt) { if (!isLt) {
this.$modal.msgError(`上传头像图片大小不能超过 ${this.fileSize} MB!`) this.$modal.msgError(`上传头像图片大小不能超过 ${this.fileSize} MB!`)
return false return false
} }
} }
this.$modal.loading("正在上传图片,请稍候...") this.$modal.loading("正在上传图片,请稍候...")
this.number++ this.number++
}, },
// 文件个数超出 // 文件个数超出
handleExceed() { handleExceed() {
this.$modal.msgError(`上传文件数量不能超过 ${this.limit} 个!`) this.$modal.msgError(`上传文件数量不能超过 ${this.limit} 个!`)
}, },
// 上传成功回调 // 上传成功回调
handleUploadSuccess(res, file) { handleUploadSuccess(res, file) {
if (res.code === 200) { if (res.code === 200) {
this.uploadList.push({ name: res.fileName, url: res.fileName }) this.uploadList.push({ name: res.fileName, url: res.fileName })
this.uploadedSuccessfully() this.uploadedSuccessfully()
} else { } else {
this.number-- this.number--
this.$modal.closeLoading() this.$modal.closeLoading()
this.$modal.msgError(res.msg) this.$modal.msgError(res.msg)
this.$refs.imageUpload.handleRemove(file) this.$refs.imageUpload.handleRemove(file)
this.uploadedSuccessfully() this.uploadedSuccessfully()
} }
}, },
// 删除图片 // 删除图片
handleDelete(file) { handleDelete(file) {
const findex = this.fileList.map(f => f.name).indexOf(file.name) const findex = this.fileList.map(f => f.name).indexOf(file.name)
if (findex > -1) { if (findex > -1) {
this.fileList.splice(findex, 1) this.fileList.splice(findex, 1)
this.$emit("input", this.listToString(this.fileList)) this.$emit("input", this.listToString(this.fileList))
} }
}, },
// 上传失败 // 上传失败
handleUploadError() { handleUploadError() {
this.$modal.msgError("上传图片失败,请重试") this.$modal.msgError("上传图片失败,请重试")
this.$modal.closeLoading() this.$modal.closeLoading()
}, },
// 上传结束处理 // 上传结束处理
uploadedSuccessfully() { uploadedSuccessfully() {
if (this.number > 0 && this.uploadList.length === this.number) { if (this.number > 0 && this.uploadList.length === this.number) {
this.fileList = this.fileList.concat(this.uploadList) this.fileList = this.fileList.concat(this.uploadList)
this.uploadList = [] this.uploadList = []
this.number = 0 this.number = 0
this.$emit("input", this.listToString(this.fileList)) this.$emit("input", this.listToString(this.fileList))
this.$modal.closeLoading() this.$modal.closeLoading()
} }
}, },
// 预览 // 预览
handlePictureCardPreview(file) { handlePictureCardPreview(file) {
this.dialogImageUrl = file.url this.dialogImageUrl = file.url
this.dialogVisible = true this.dialogVisible = true
}, },
// 对象转成指定字符串分隔 // 对象转成指定字符串分隔
listToString(list, separator) { listToString(list, separator) {
let strs = "" let strs = ""
separator = separator || "," separator = separator || ","
for (let i in list) { for (let i in list) {
if (list[i].url) { if (list[i].url) {
strs += list[i].url.replace(this.baseUrl, "") + separator strs += list[i].url.replace(this.baseUrl, "") + separator
} }
} }
return strs != '' ? strs.substr(0, strs.length - 1) : '' return strs != '' ? strs.substr(0, strs.length - 1) : ''
} }
} }
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
// .el-upload--picture-card 控制加号部分 // .el-upload--picture-card 控制加号部分
::v-deep.hide .el-upload--picture-card { ::v-deep.hide .el-upload--picture-card {
display: none; display: none;
} }
::v-deep .el-upload-list--picture-card.is-disabled + .el-upload--picture-card { ::v-deep .el-upload-list--picture-card.is-disabled + .el-upload--picture-card {
display: none !important; display: none !important;
} }
// 去掉动画效果 // 去掉动画效果
::v-deep .el-list-enter-active, ::v-deep .el-list-enter-active,
::v-deep .el-list-leave-active { ::v-deep .el-list-leave-active {
transition: all 0s; transition: all 0s;
} }
::v-deep .el-list-enter, .el-list-leave-active { ::v-deep .el-list-enter, .el-list-leave-active {
opacity: 0; opacity: 0;
transform: translateY(0); transform: translateY(0);
} }
</style> </style>

View File

@@ -1,113 +1,113 @@
<template> <template>
<div :class="{'hidden':hidden}" class="pagination-container"> <div :class="{'hidden':hidden}" class="pagination-container">
<el-pagination <el-pagination
:background="background" :background="background"
:current-page.sync="currentPage" :current-page.sync="currentPage"
:page-size.sync="pageSize" :page-size.sync="pageSize"
:layout="layout" :layout="layout"
:page-sizes="pageSizes" :page-sizes="pageSizes"
:pager-count="pagerCount" :pager-count="pagerCount"
:total="total" :total="total"
v-bind="$attrs" v-bind="$attrs"
@size-change="handleSizeChange" @size-change="handleSizeChange"
@current-change="handleCurrentChange" @current-change="handleCurrentChange"
/> />
</div> </div>
</template> </template>
<script> <script>
import { scrollTo } from '@/utils/scroll-to' import { scrollTo } from '@/utils/scroll-to'
export default { export default {
name: 'Pagination', name: 'Pagination',
props: { props: {
total: { total: {
required: true, required: true,
type: Number type: Number
}, },
page: { page: {
type: Number, type: Number,
default: 1 default: 1
}, },
limit: { limit: {
type: Number, type: Number,
default: 20 default: 20
}, },
pageSizes: { pageSizes: {
type: Array, type: Array,
default() { default() {
return [10, 20, 30, 50] return [10, 20, 30, 50]
} }
}, },
// 移动端页码按钮的数量端默认值5 // 移动端页码按钮的数量端默认值5
pagerCount: { pagerCount: {
type: Number, type: Number,
default: document.body.clientWidth < 992 ? 5 : 7 default: document.body.clientWidth < 992 ? 5 : 7
}, },
layout: { layout: {
type: String, type: String,
default: 'total, sizes, prev, pager, next, jumper' default: 'total, sizes, prev, pager, next, jumper'
}, },
background: { background: {
type: Boolean, type: Boolean,
default: true default: true
}, },
autoScroll: { autoScroll: {
type: Boolean, type: Boolean,
default: true default: true
}, },
hidden: { hidden: {
type: Boolean, type: Boolean,
default: false default: false
} }
}, },
data() { data() {
return { return {
} }
}, },
computed: { computed: {
currentPage: { currentPage: {
get() { get() {
return this.page return this.page
}, },
set(val) { set(val) {
this.$emit('update:page', val) this.$emit('update:page', val)
} }
}, },
pageSize: { pageSize: {
get() { get() {
return this.limit return this.limit
}, },
set(val) { set(val) {
this.$emit('update:limit', val) this.$emit('update:limit', val)
} }
} }
}, },
methods: { methods: {
handleSizeChange(val) { handleSizeChange(val) {
if (this.currentPage * val > this.total) { if (this.currentPage * val > this.total) {
this.currentPage = 1 this.currentPage = 1
} }
this.$emit('pagination', { page: this.currentPage, limit: val }) this.$emit('pagination', { page: this.currentPage, limit: val })
if (this.autoScroll) { if (this.autoScroll) {
scrollTo(0, 800) scrollTo(0, 800)
} }
}, },
handleCurrentChange(val) { handleCurrentChange(val) {
this.$emit('pagination', { page: val, limit: this.pageSize }) this.$emit('pagination', { page: val, limit: this.pageSize })
if (this.autoScroll) { if (this.autoScroll) {
scrollTo(0, 800) scrollTo(0, 800)
} }
} }
} }
} }
</script> </script>
<style scoped> <style scoped>
.pagination-container { .pagination-container {
background: #fff; background: #fff;
} }
.pagination-container.hidden { .pagination-container.hidden {
display: none; display: none;
} }
</style> </style>

View File

@@ -1,141 +1,141 @@
<template> <template>
<div :style="{zIndex:zIndex,height:height,width:width}" class="pan-item"> <div :style="{zIndex:zIndex,height:height,width:width}" class="pan-item">
<div class="pan-info"> <div class="pan-info">
<div class="pan-info-roles-container"> <div class="pan-info-roles-container">
<slot /> <slot />
</div> </div>
</div> </div>
<div :style="{backgroundImage: `url(${image})`}" class="pan-thumb"></div> <div :style="{backgroundImage: `url(${image})`}" class="pan-thumb"></div>
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'PanThumb', name: 'PanThumb',
props: { props: {
image: { image: {
type: String, type: String,
required: true required: true
}, },
zIndex: { zIndex: {
type: Number, type: Number,
default: 1 default: 1
}, },
width: { width: {
type: String, type: String,
default: '150px' default: '150px'
}, },
height: { height: {
type: String, type: String,
default: '150px' default: '150px'
} }
} }
} }
</script> </script>
<style scoped> <style scoped>
.pan-item { .pan-item {
width: 200px; width: 200px;
height: 200px; height: 200px;
border-radius: 50%; border-radius: 50%;
display: inline-block; display: inline-block;
position: relative; position: relative;
cursor: default; cursor: default;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2); box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
} }
.pan-info-roles-container { .pan-info-roles-container {
padding: 20px; padding: 20px;
text-align: center; text-align: center;
} }
.pan-thumb { .pan-thumb {
width: 100%; width: 100%;
height: 100%; height: 100%;
background-position: center center; background-position: center center;
background-size: cover; background-size: cover;
border-radius: 50%; border-radius: 50%;
overflow: hidden; overflow: hidden;
position: absolute; position: absolute;
transform-origin: 95% 40%; transform-origin: 95% 40%;
transition: all 0.3s ease-in-out; transition: all 0.3s ease-in-out;
} }
/* .pan-thumb:after { /* .pan-thumb:after {
content: ''; content: '';
width: 8px; width: 8px;
height: 8px; height: 8px;
position: absolute; position: absolute;
border-radius: 50%; border-radius: 50%;
top: 40%; top: 40%;
left: 95%; left: 95%;
margin: -4px 0 0 -4px; margin: -4px 0 0 -4px;
background: radial-gradient(ellipse at center, rgba(14, 14, 14, 1) 0%, rgba(125, 126, 125, 1) 100%); background: radial-gradient(ellipse at center, rgba(14, 14, 14, 1) 0%, rgba(125, 126, 125, 1) 100%);
box-shadow: 0 0 1px rgba(255, 255, 255, 0.9); box-shadow: 0 0 1px rgba(255, 255, 255, 0.9);
} */ } */
.pan-info { .pan-info {
position: absolute; position: absolute;
width: inherit; width: inherit;
height: inherit; height: inherit;
border-radius: 50%; border-radius: 50%;
overflow: hidden; overflow: hidden;
box-shadow: inset 0 0 0 5px rgba(0, 0, 0, 0.05); box-shadow: inset 0 0 0 5px rgba(0, 0, 0, 0.05);
} }
.pan-info h3 { .pan-info h3 {
color: #fff; color: #fff;
text-transform: uppercase; text-transform: uppercase;
position: relative; position: relative;
letter-spacing: 2px; letter-spacing: 2px;
font-size: 18px; font-size: 18px;
margin: 0 60px; margin: 0 60px;
padding: 22px 0 0 0; padding: 22px 0 0 0;
height: 85px; height: 85px;
font-family: 'Open Sans', Arial, sans-serif; font-family: 'Open Sans', Arial, sans-serif;
text-shadow: 0 0 1px #fff, 0 1px 2px rgba(0, 0, 0, 0.3); text-shadow: 0 0 1px #fff, 0 1px 2px rgba(0, 0, 0, 0.3);
} }
.pan-info p { .pan-info p {
color: #fff; color: #fff;
padding: 10px 5px; padding: 10px 5px;
font-style: italic; font-style: italic;
margin: 0 30px; margin: 0 30px;
font-size: 12px; font-size: 12px;
border-top: 1px solid rgba(255, 255, 255, 0.5); border-top: 1px solid rgba(255, 255, 255, 0.5);
} }
.pan-info p a { .pan-info p a {
display: block; display: block;
color: #333; color: #333;
width: 80px; width: 80px;
height: 80px; height: 80px;
background: rgba(255, 255, 255, 0.3); background: rgba(255, 255, 255, 0.3);
border-radius: 50%; border-radius: 50%;
color: #fff; color: #fff;
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
text-transform: uppercase; text-transform: uppercase;
font-size: 9px; font-size: 9px;
letter-spacing: 1px; letter-spacing: 1px;
padding-top: 24px; padding-top: 24px;
margin: 7px auto 0; margin: 7px auto 0;
font-family: 'Open Sans', Arial, sans-serif; font-family: 'Open Sans', Arial, sans-serif;
opacity: 0; opacity: 0;
transition: transform 0.3s ease-in-out 0.2s, opacity 0.3s ease-in-out 0.2s, background 0.2s linear 0s; transition: transform 0.3s ease-in-out 0.2s, opacity 0.3s ease-in-out 0.2s, background 0.2s linear 0s;
transform: translateX(60px) rotate(90deg); transform: translateX(60px) rotate(90deg);
} }
.pan-info p a:hover { .pan-info p a:hover {
background: rgba(255, 255, 255, 0.5); background: rgba(255, 255, 255, 0.5);
} }
.pan-item:hover .pan-thumb { .pan-item:hover .pan-thumb {
transform: rotate(-110deg); transform: rotate(-110deg);
} }
.pan-item:hover .pan-info p a { .pan-item:hover .pan-info p a {
opacity: 1; opacity: 1;
transform: translateX(0px) rotate(0deg); transform: translateX(0px) rotate(0deg);
} }
</style> </style>

View File

@@ -1,253 +1,186 @@
<template> <template>
<div class="top-right-btn" :style="style"> <div class="top-right-btn" :style="style">
<el-row> <el-row>
<el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top" v-if="search"> <el-tooltip class="item" effect="dark" :content="showSearch ? '隐藏搜索' : '显示搜索'" placement="top" v-if="search">
<el-button size="mini" circle icon="el-icon-search" @click="toggleSearch()" /> <el-button size="mini" circle icon="el-icon-search" @click="toggleSearch()" />
</el-tooltip> </el-tooltip>
<el-tooltip class="item" effect="dark" content="刷新" placement="top"> <el-tooltip class="item" effect="dark" content="刷新" placement="top">
<el-button size="mini" circle icon="el-icon-refresh" @click="refresh()" /> <el-button size="mini" circle icon="el-icon-refresh" @click="refresh()" />
</el-tooltip> </el-tooltip>
<el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="Object.keys(columns).length > 0"> <el-tooltip class="item" effect="dark" content="显隐列" placement="top" v-if="Object.keys(columns).length > 0">
<el-button size="mini" circle icon="el-icon-menu" @click="showColumn()" v-if="showColumnsType == 'transfer'"/> <el-button size="mini" circle icon="el-icon-menu" @click="showColumn()" v-if="showColumnsType == 'transfer'"/>
<el-dropdown trigger="click" :hide-on-click="false" style="padding-left: 12px" v-if="showColumnsType == 'checkbox'"> <el-dropdown trigger="click" :hide-on-click="false" style="padding-left: 12px" v-if="showColumnsType == 'checkbox'">
<el-button size="mini" circle icon="el-icon-menu" /> <el-button size="mini" circle icon="el-icon-menu" />
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
<!-- 全选/反选 按钮 --> <!-- 全选/反选 按钮 -->
<el-dropdown-item> <el-dropdown-item>
<el-checkbox :indeterminate="isIndeterminate" v-model="isChecked" @change="toggleCheckAll"> 列展示 </el-checkbox> <el-checkbox :indeterminate="isIndeterminate" v-model="isChecked" @change="toggleCheckAll"> 列展示 </el-checkbox>
</el-dropdown-item> </el-dropdown-item>
<div class="check-line"></div> <div class="check-line"></div>
<template v-for="(item, key) in columns"> <template v-for="(item, key) in columns">
<el-dropdown-item :key="key"> <el-dropdown-item :key="key">
<el-checkbox v-model="item.visible" @change="checkboxChange($event, key)" :label="item.label" /> <el-checkbox v-model="item.visible" @change="checkboxChange($event, key)" :label="item.label" />
</el-dropdown-item> </el-dropdown-item>
</template> </template>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
</el-tooltip> </el-tooltip>
</el-row> </el-row>
<el-dialog :title="title" :visible.sync="open" append-to-body> <el-dialog :title="title" :visible.sync="open" append-to-body>
<el-transfer <el-transfer
:titles="['显示', '隐藏']" :titles="['显示', '隐藏']"
v-model="value" v-model="value"
:data="transferData" :data="transferData"
@change="dataChange" @change="dataChange"
></el-transfer> ></el-transfer>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script> <script>
import cache from '@/plugins/cache' export default {
name: "RightToolbar",
export default { data() {
name: "RightToolbar", return {
data() { // 显隐数据
return { value: [],
// 显隐数据 // 弹出层标题
value: [], title: "显示/隐藏",
// 弹出层标题 // 是否显示弹出层
title: "显示/隐藏", open: false
// 是否显示弹出层 }
open: false },
} props: {
}, /* 是否显示检索条件 */
props: { showSearch: {
/* 是否显示检索条件 */ type: Boolean,
showSearch: { default: true
type: Boolean, },
default: true /* 显隐列信息(数组格式、对象格式) */
}, columns: {
/* 显隐列信息(数组格式、对象格式) */ type: [Array, Object],
columns: { default: () => ({})
type: [Array, Object], },
default: () => ({}) /* 是否显示检索图标 */
}, search: {
/* 是否显示检索图标 */ type: Boolean,
search: { default: true
type: Boolean, },
default: true /* 显隐列类型transfer穿梭框、checkbox复选框 */
}, showColumnsType: {
/* 显隐列类型transfer穿梭框、checkbox复选框 */ type: String,
showColumnsType: { default: "checkbox"
type: String, },
default: "checkbox" /* 右外边距 */
}, gutter: {
/* 右外边距 */ type: Number,
gutter: { default: 10
type: Number, },
default: 10 },
}, computed: {
/* 列显隐状态记忆的 localStorage key传入则启用记忆不传则不记忆 */ style() {
storageKey: { const ret = {}
type: String, if (this.gutter) {
default: "" ret.marginRight = `${this.gutter / 2}px`
} }
}, return ret
computed: { },
style() { isChecked: {
const ret = {} get() {
if (this.gutter) { return Array.isArray(this.columns) ? this.columns.every((col) => col.visible) : Object.values(this.columns).every((col) => col.visible)
ret.marginRight = `${this.gutter / 2}px` },
} set() {}
return ret },
}, isIndeterminate() {
isChecked: { return Array.isArray(this.columns) ? this.columns.some((col) => col.visible) && !this.isChecked : Object.values(this.columns).some((col) => col.visible) && !this.isChecked
get() { },
return Array.isArray(this.columns) ? this.columns.every((col) => col.visible) : Object.values(this.columns).every((col) => col.visible) transferData() {
}, if (Array.isArray(this.columns)) {
set() {} return this.columns.map((item, index) => ({ key: index, label: item.label }))
}, } else {
isIndeterminate() { return Object.keys(this.columns).map((key, index) => ({ key: index, label: this.columns[key].label }))
return Array.isArray(this.columns) ? this.columns.some((col) => col.visible) && !this.isChecked : Object.values(this.columns).some((col) => col.visible) && !this.isChecked }
}, }
transferData() { },
if (Array.isArray(this.columns)) { created() {
return this.columns.map((item, index) => ({ key: index, label: item.label })) if (this.showColumnsType == 'transfer') {
} else { // transfer穿梭显隐列初始默认隐藏列
return Object.keys(this.columns).map((key, index) => ({ key: index, label: this.columns[key].label })) if (Array.isArray(this.columns)) {
} for (let item in this.columns) {
} if (this.columns[item].visible === false) {
}, this.value.push(parseInt(item))
created() { }
// 如果传入了 storageKey从 localStorage 恢复列显隐状态 }
if (this.storageKey) { } else {
try { Object.keys(this.columns).forEach((key, index) => {
const saved = cache.local.getJSON(this.storageKey) if (this.columns[key].visible === false) {
if (saved && typeof saved === 'object') { this.value.push(index)
if (Array.isArray(this.columns)) { }
this.columns.forEach((col, index) => { })
if (saved[index] !== undefined) col.visible = saved[index] }
}) }
} else { },
Object.keys(this.columns).forEach(key => { methods: {
if (saved[key] !== undefined) this.columns[key].visible = saved[key] // 搜索
}) toggleSearch() {
} this.$emit("update:showSearch", !this.showSearch)
} },
} catch (e) {} // 刷新
} refresh() {
if (this.showColumnsType == 'transfer') { this.$emit("queryTable")
// transfer穿梭显隐列初始默认隐藏列 },
if (Array.isArray(this.columns)) { // 右侧列表元素变化
for (let item in this.columns) { dataChange(data) {
if (this.columns[item].visible === false) { if (Array.isArray(this.columns)) {
this.value.push(parseInt(item)) for (let item in this.columns) {
} const key = this.columns[item].key
} this.columns[item].visible = !data.includes(key)
} else { }
Object.keys(this.columns).forEach((key, index) => { } else {
if (this.columns[key].visible === false) { Object.keys(this.columns).forEach((key, index) => {
this.value.push(index) this.columns[key].visible = !data.includes(index)
} })
}) }
} },
} // 打开显隐列dialog
}, showColumn() {
methods: { this.open = true
// 搜索 },
toggleSearch() { // 单勾选
let el = this.$el checkboxChange(event, key) {
let formEl = null if (Array.isArray(this.columns)) {
while ((el = el.parentElement) && el !== document.body) { this.columns.filter(item => item.key == key)[0].visible = event
if ((formEl = el.querySelector('.el-form'))) break } else {
} this.columns[key].visible = event
if (!formEl) return this.$emit('update:showSearch', !this.showSearch) }
this._animateSearch(formEl, this.showSearch) },
}, // 切换全选/反选
// 搜索栏动画 toggleCheckAll() {
_animateSearch(el, isHide) { const newValue = !this.isChecked
const DURATION = 260 if (Array.isArray(this.columns)) {
const TRANSITION = 'max-height 0.25s ease, opacity 0.2s ease' this.columns.forEach((col) => (col.visible = newValue))
const clear = () => Object.assign(el.style, { transition: '', maxHeight: '', opacity: '', overflow: '' }) } else {
Object.assign(el.style, { overflow: 'hidden', transition: '' }) Object.values(this.columns).forEach((col) => (col.visible = newValue))
if (isHide) { }
Object.assign(el.style, { maxHeight: el.scrollHeight + 'px', opacity: '1', transition: TRANSITION }) }
requestAnimationFrame(() => Object.assign(el.style, { maxHeight: '0', opacity: '0' })) },
setTimeout(() => { this.$emit('update:showSearch', false); clear() }, DURATION) }
} else { </script>
this.$emit('update:showSearch', true)
this.$nextTick(() => { <style lang="scss" scoped>
Object.assign(el.style, { maxHeight: '0', opacity: '0' }) ::v-deep .el-transfer__button {
requestAnimationFrame(() => requestAnimationFrame(() => { border-radius: 50%;
Object.assign(el.style, { transition: TRANSITION, maxHeight: el.scrollHeight + 'px', opacity: '1' }) padding: 12px;
})) display: block;
setTimeout(clear, DURATION) margin-left: 0px;
}) }
} ::v-deep .el-transfer__button:first-child {
}, margin-bottom: 10px;
// 刷新 }
refresh() { .check-line {
this.$emit("queryTable") width: 90%;
}, height: 1px;
// 右侧列表元素变化 background-color: #ccc;
dataChange(data) { margin: 3px auto;
if (Array.isArray(this.columns)) { }
for (let item in this.columns) { </style>
const key = this.columns[item].key
this.columns[item].visible = !data.includes(key)
}
} else {
Object.keys(this.columns).forEach((key, index) => {
this.columns[key].visible = !data.includes(index)
})
}
this.saveStorage()
},
// 打开显隐列dialog
showColumn() {
this.open = true
},
// 单勾选
checkboxChange(event, key) {
if (Array.isArray(this.columns)) {
this.columns.filter(item => item.key == key)[0].visible = event
} else {
this.columns[key].visible = event
}
this.saveStorage()
},
// 切换全选/反选
toggleCheckAll() {
const newValue = !this.isChecked
if (Array.isArray(this.columns)) {
this.columns.forEach((col) => (col.visible = newValue))
} else {
Object.values(this.columns).forEach((col) => (col.visible = newValue))
}
this.saveStorage()
},
// 将当前列显隐状态持久化到 localStorage
saveStorage() {
if (!this.storageKey) return
try {
let state = {}
if (Array.isArray(this.columns)) {
this.columns.forEach((col, index) => { state[index] = col.visible })
} else {
Object.keys(this.columns).forEach(key => { state[key] = this.columns[key].visible })
}
cache.local.setJSON(this.storageKey, state)
} catch (e) {}
}
},
}
</script>
<style lang="scss" scoped>
::v-deep .el-transfer__button {
border-radius: 50%;
padding: 12px;
display: block;
margin-left: 0px;
}
::v-deep .el-transfer__button:first-child {
margin-bottom: 10px;
}
.check-line {
width: 90%;
height: 1px;
background-color: #ccc;
margin: 3px auto;
}
</style>

View File

@@ -1,21 +1,21 @@
<template> <template>
<div> <div>
<svg-icon icon-class="question" @click="goto" /> <svg-icon icon-class="question" @click="goto" />
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'RuoYiDoc', name: 'RuoYiDoc',
data() { data() {
return { return {
url: 'http://doc.ruoyi.vip/ruoyi-vue' url: 'http://doc.ruoyi.vip/ruoyi-vue'
} }
}, },
methods: { methods: {
goto() { goto() {
window.open(this.url) window.open(this.url)
} }
} }
} }
</script> </script>

View File

@@ -1,21 +1,21 @@
<template> <template>
<div> <div>
<svg-icon icon-class="github" @click="goto" /> <svg-icon icon-class="github" @click="goto" />
</div> </div>
</template> </template>
<script> <script>
export default { export default {
name: 'RuoYiGit', name: 'RuoYiGit',
data() { data() {
return { return {
url: 'https://gitee.com/y_project/RuoYi-Vue' url: 'https://gitee.com/y_project/RuoYi-Vue'
} }
}, },
methods: { methods: {
goto() { goto() {
window.open(this.url) window.open(this.url)
} }
} }
} }
</script> </script>

View File

@@ -1,57 +1,57 @@
<template> <template>
<div> <div>
<svg-icon :icon-class="isFullscreen?'exit-fullscreen':'fullscreen'" @click="click" /> <svg-icon :icon-class="isFullscreen?'exit-fullscreen':'fullscreen'" @click="click" />
</div> </div>
</template> </template>
<script> <script>
import screenfull from 'screenfull' import screenfull from 'screenfull'
export default { export default {
name: 'Screenfull', name: 'Screenfull',
data() { data() {
return { return {
isFullscreen: false isFullscreen: false
} }
}, },
mounted() { mounted() {
this.init() this.init()
}, },
beforeDestroy() { beforeDestroy() {
this.destroy() this.destroy()
}, },
methods: { methods: {
click() { click() {
if (!screenfull.isEnabled) { if (!screenfull.isEnabled) {
this.$message({ message: '你的浏览器不支持全屏', type: 'warning' }) this.$message({ message: '你的浏览器不支持全屏', type: 'warning' })
return false return false
} }
screenfull.toggle() screenfull.toggle()
}, },
change() { change() {
this.isFullscreen = screenfull.isFullscreen this.isFullscreen = screenfull.isFullscreen
}, },
init() { init() {
if (screenfull.isEnabled) { if (screenfull.isEnabled) {
screenfull.on('change', this.change) screenfull.on('change', this.change)
} }
}, },
destroy() { destroy() {
if (screenfull.isEnabled) { if (screenfull.isEnabled) {
screenfull.off('change', this.change) screenfull.off('change', this.change)
} }
} }
} }
} }
</script> </script>
<style scoped> <style scoped>
.screenfull-svg { .screenfull-svg {
display: inline-block; display: inline-block;
cursor: pointer; cursor: pointer;
fill: #5a5e66;; fill: #5a5e66;;
width: 20px; width: 20px;
height: 20px; height: 20px;
vertical-align: 10px; vertical-align: 10px;
} }
</style> </style>

View File

@@ -1,55 +1,55 @@
<template> <template>
<el-dropdown trigger="click" @command="handleSetSize"> <el-dropdown trigger="click" @command="handleSetSize">
<div> <div>
<svg-icon class-name="size-icon" icon-class="size" /> <svg-icon class-name="size-icon" icon-class="size" />
</div> </div>
<el-dropdown-menu slot="dropdown"> <el-dropdown-menu slot="dropdown">
<el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size===item.value" :command="item.value"> <el-dropdown-item v-for="item of sizeOptions" :key="item.value" :disabled="size===item.value" :command="item.value">
{{ item.label }} {{ item.label }}
</el-dropdown-item> </el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</el-dropdown> </el-dropdown>
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
sizeOptions: [ sizeOptions: [
{ label: 'Default', value: 'default' }, { label: 'Default', value: 'default' },
{ label: 'Medium', value: 'medium' }, { label: 'Medium', value: 'medium' },
{ label: 'Small', value: 'small' }, { label: 'Small', value: 'small' },
{ label: 'Mini', value: 'mini' } { label: 'Mini', value: 'mini' }
] ]
} }
}, },
computed: { computed: {
size() { size() {
return this.$store.getters.size return this.$store.getters.size
} }
}, },
methods: { methods: {
handleSetSize(size) { handleSetSize(size) {
this.$ELEMENT.size = size this.$ELEMENT.size = size
this.$store.dispatch('app/setSize', size) this.$store.dispatch('app/setSize', size)
this.refreshView() this.refreshView()
this.$message({ this.$message({
message: 'Switch Size Success', message: 'Switch Size Success',
type: 'success' type: 'success'
}) })
}, },
refreshView() { refreshView() {
// In order to make the cached page re-rendered // In order to make the cached page re-rendered
this.$store.dispatch('tagsView/delAllCachedViews', this.$route) this.$store.dispatch('tagsView/delAllCachedViews', this.$route)
const { fullPath } = this.$route const { fullPath } = this.$route
this.$nextTick(() => { this.$nextTick(() => {
this.$router.replace({ this.$router.replace({
path: '/redirect' + fullPath path: '/redirect' + fullPath
}) })
}) })
} }
} }
} }
</script> </script>

View File

@@ -1,61 +1,61 @@
<template> <template>
<div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" /> <div v-if="isExternal" :style="styleExternalIcon" class="svg-external-icon svg-icon" v-on="$listeners" />
<svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners"> <svg v-else :class="svgClass" aria-hidden="true" v-on="$listeners">
<use :xlink:href="iconName" /> <use :xlink:href="iconName" />
</svg> </svg>
</template> </template>
<script> <script>
import { isExternal } from '@/utils/validate' import { isExternal } from '@/utils/validate'
export default { export default {
name: 'SvgIcon', name: 'SvgIcon',
props: { props: {
iconClass: { iconClass: {
type: String, type: String,
required: true required: true
}, },
className: { className: {
type: String, type: String,
default: '' default: ''
} }
}, },
computed: { computed: {
isExternal() { isExternal() {
return isExternal(this.iconClass) return isExternal(this.iconClass)
}, },
iconName() { iconName() {
return `#icon-${this.iconClass}` return `#icon-${this.iconClass}`
}, },
svgClass() { svgClass() {
if (this.className) { if (this.className) {
return 'svg-icon ' + this.className return 'svg-icon ' + this.className
} else { } else {
return 'svg-icon' return 'svg-icon'
} }
}, },
styleExternalIcon() { styleExternalIcon() {
return { return {
mask: `url(${this.iconClass}) no-repeat 50% 50%`, mask: `url(${this.iconClass}) no-repeat 50% 50%`,
'-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%` '-webkit-mask': `url(${this.iconClass}) no-repeat 50% 50%`
} }
} }
} }
} }
</script> </script>
<style scoped> <style scoped>
.svg-icon { .svg-icon {
width: 1em; width: 1em;
height: 1em; height: 1em;
vertical-align: -0.15em; vertical-align: -0.15em;
fill: currentColor; fill: currentColor;
overflow: hidden; overflow: hidden;
} }
.svg-external-icon { .svg-external-icon {
background-color: currentColor; background-color: currentColor;
mask-size: cover!important; mask-size: cover!important;
display: inline-block; display: inline-block;
} }
</style> </style>

View File

@@ -1,709 +0,0 @@
<template>
<div class="tree-sidebar" :class="{ collapsed: collapsed, resizing: isResizing, 'no-initial-transition': isLoadingFromStorage}" :style="{ width: sidebarWidth + 'px' }">
<!-- 右侧拖动条 -->
<div v-if="!collapsed" class="resize-handle" @mousedown="startResize" @touchstart="startResize" :class="{ active: isResizing }" />
<div class="tree-header">
<span class="tree-title" v-show="!collapsed">
<i :class="titleIconClass"></i> {{ title }}
</span>
<div class="tree-actions" v-show="!collapsed">
<el-tooltip :content="isExpandedAll ? '收起全部' : '展开全部'" placement="right">
<i class="tree-action-icon" :class="isExpandedAll ? 'el-icon-arrow-down' : 'el-icon-arrow-up'" @click="toggleExpandAll" />
</el-tooltip>
<el-tooltip content="刷新" placement="right">
<i class="tree-action-icon el-icon-refresh" @click="handleRefresh" />
</el-tooltip>
<slot name="actions"></slot>
</div>
</div>
<!-- 侧边栏展开/收起按钮 -->
<div class="collapse-button-container">
<el-tooltip :content="collapsed ? '展开' : '收起'" placement="right">
<i class="collapse-button" :class="collapsed ? 'el-icon-d-arrow-right' : 'el-icon-d-arrow-left'" @click="toggleCollapsed" />
</el-tooltip>
</div>
<div class="tree-search" v-show="!collapsed" v-if="showSearch">
<el-input v-model="searchKeyword" :placeholder="searchPlaceholder" clearable size="small" prefix-icon="el-icon-search" @input="onSearch" />
</div>
<div class="tree-wrap" v-show="!collapsed">
<el-tree
ref="treeRef"
:data="treeData"
:props="treeProps"
:expand-on-click-node="expandOnClickNode"
:filter-node-method="filterNodeMethod"
:default-expand-all="defaultExpandAll"
:default-expanded-keys="defaultExpandedKeys"
:node-key="nodeKey"
:check-strictly="checkStrictly"
:show-checkbox="showCheckbox"
@node-click="onNodeClick"
@check="onCheck"
@node-expand="onNodeExpand"
@node-collapse="onNodeCollapse"
>
<span class="tree-node" slot-scope="{ node, data }">
<slot name="node" :node="node" :data="data">
<i :class="data.children && data.children.length ? 'el-icon-folder' : 'el-icon-document'" class="node-icon" />
<span class="node-label" :title="node.label">{{ node.label }}</span>
</slot>
</span>
</el-tree>
</div>
</div>
</template>
<script>
export default {
name: "TreeSidebar",
props: {
// 树形数据
treeData: {
type: Array,
default: () => []
},
// 标题
title: {
type: String,
default: '树形结构'
},
// 标题图标类名
titleIconClass: {
type: String,
default: 'el-icon-office-building'
},
// 是否显示搜索框
showSearch: {
type: Boolean,
default: true
},
// 搜索框占位符
searchPlaceholder: {
type: String,
default: '请输入名称'
},
// 是否默认收起侧边栏
defaultCollapsed: {
type: Boolean,
default: false
},
// 树配置项
treeProps: {
type: Object,
default: () => ({
children: "children",
label: "label"
})
},
// 节点唯一标识字段
nodeKey: {
type: String,
default: 'id'
},
// 是否在点击节点时展开或收起
expandOnClickNode: {
type: Boolean,
default: false
},
// 是否显示复选框
showCheckbox: {
type: Boolean,
default: false
},
// 是否严格的遵循父子不互相关联
checkStrictly: {
type: Boolean,
default: false
},
// 是否默认展开所有节点
defaultExpandAll: {
type: Boolean,
default: false
},
// 默认展开的节点的key数组
defaultExpandedKeys: {
type: Array,
default: () => []
},
// 默认宽度
defaultWidth: {
type: Number,
default: 220
},
// 收起时的宽度
collapsedWidth: {
type: Number,
default: 20
},
// 最小宽度
minWidth: {
type: Number,
default: 180
},
// 最大宽度
maxWidth: {
type: Number,
default: 400
},
// 本地存储的宽度key
storageKey: {
type: String,
default: 'tree-sidebar-width'
},
// 是否启用本地存储宽度
enableStorage: {
type: Boolean,
default: true
},
// 自定义过滤方法
filterMethod: {
type: Function,
default: null
}
},
data() {
return {
searchKeyword: "",
collapsed: this.defaultCollapsed,
sidebarWidth: this.defaultCollapsed ? this.collapsedWidth : this.defaultWidth,
isResizing: false,
startX: 0,
startWidth: 0,
saveWidthTimer: null,
rafId: null,
isLoadingFromStorage: false,
expandedAll: this.defaultExpandAll
};
},
computed: {
// 计算当前是否全部展开
isExpandedAll: {
get() {
return this.expandedAll;
},
set(val) {
this.expandedAll = val;
}
}
},
watch: {
collapsed(newVal, oldVal) {
if (newVal !== oldVal) {
this.handleCollapseChange(newVal);
this.$emit("collapsed-change", newVal);
}
},
// 监听内部展开状态变化,触发实际树的展开/收起
expandedAll(newVal) {
this.$nextTick(() => {
if (newVal) {
this.expandAllNodes();
} else {
this.collapseAllNodes();
}
});
this.$emit("expanded-all-change", newVal);
},
// 监听搜索关键词
searchKeyword(val) {
if (this.$refs.treeRef) {
this.$refs.treeRef.filter(val);
this.$emit("search", val);
}
}
},
mounted() {
this.isLoadingFromStorage = true
if (!this.collapsed && this.enableStorage) {
const savedWidth = this.getSavedWidth();
if (savedWidth !== null) {
this.sidebarWidth = savedWidth;
}
}
this.$nextTick(() => {
this.isLoadingFromStorage = false
})
if (this.expandedAll) {
this.$nextTick(() => {
this.expandAllNodes();
});
}
},
beforeDestroy() {
this.cleanup();
},
methods: {
// 节点过滤方法
filterNodeMethod(value, data) {
if (this.filterMethod) {
return this.filterMethod(value, data);
}
if (!value) return true;
return data.label && data.label.indexOf(value) !== -1;
},
// 清理定时器和动画帧
cleanup() {
if (this.rafId) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
if (this.saveWidthTimer) {
clearTimeout(this.saveWidthTimer);
this.saveWidthTimer = null;
}
},
// 处理收起/展开状态变化
handleCollapseChange(isCollapsed) {
if (isCollapsed) {
this.saveWidthToStorage();
this.sidebarWidth = this.collapsedWidth;
} else {
const savedWidth = this.getSavedWidth();
this.sidebarWidth = savedWidth !== null ? savedWidth : this.defaultWidth;
}
},
// 获取保存的宽度
getSavedWidth() {
if (!this.enableStorage) {
return null;
}
try {
const savedWidth = localStorage.getItem(this.storageKey);
if (savedWidth) {
const width = parseInt(savedWidth, 10);
if (!isNaN(width) && width >= this.minWidth && width <= this.maxWidth) {
return width;
}
}
} catch (error) {
console.warn(`Failed to load sidebar width from storage with key ${this.storageKey}:`, error);
}
return null;
},
// 保存宽度到本地存储
saveWidthToStorage() {
if (this.collapsed || !this.enableStorage) return;
try {
localStorage.setItem(this.storageKey, this.sidebarWidth.toString());
} catch (error) {
console.warn(`Failed to save sidebar width to storage with key ${this.storageKey}:`, error);
}
},
// 切换侧边栏收起/展开状态
toggleCollapsed() {
this.collapsed = !this.collapsed;
},
// 切换展开/折叠所有节点
toggleExpandAll() {
this.isExpandedAll = !this.isExpandedAll;
},
// 展开所有节点
expandAllNodes() {
if (!this.$refs.treeRef) return;
const allNodes = this.getAllNodes(this.$refs.treeRef.root);
allNodes.forEach(node => {
if (node.expanded !== undefined && !node.expanded) {
node.expanded = true;
}
});
},
// 获取所有节点
getAllNodes(rootNode) {
const nodes = [];
const traverse = (node) => {
if (!node) return;
nodes.push(node);
if (node.childNodes && node.childNodes.length) {
node.childNodes.forEach(child => traverse(child));
}
};
traverse(rootNode);
return nodes;
},
// 收起所有节点
collapseAllNodes() {
if (!this.$refs.treeRef) return;
const allNodes = this.getAllNodes(this.$refs.treeRef.root);
allNodes.forEach(node => {
if (node.expanded !== undefined && node.expanded) {
node.expanded = false;
}
});
},
// 处理刷新操作
handleRefresh() {
this.$emit("refresh");
},
// 节点点击事件
onNodeClick(data, node, e) {
this.$emit("node-click", data, node, e);
},
// 复选框选中事件
onCheck(data, checkedInfo) {
this.$emit("check", data, checkedInfo);
},
// 节点展开事件
onNodeExpand(data, node, e) {
this.$emit("node-expand", data, node, e);
},
// 节点折叠事件
onNodeCollapse(data, node, e) {
this.$emit("node-collapse", data, node, e);
},
// 搜索处理
onSearch() {
// 搜索逻辑已在 watch 中处理
},
// 设置当前选中的节点
setCurrentKey(key) {
if (this.$refs.treeRef) {
this.$refs.treeRef.setCurrentKey(key);
}
},
// 获取当前选中的节点
getCurrentNode() {
if (this.$refs.treeRef) {
return this.$refs.treeRef.getCurrentNode();
}
return null;
},
// 获取当前选中的节点的key
getCurrentKey() {
if (this.$refs.treeRef) {
return this.$refs.treeRef.getCurrentKey();
}
return null;
},
// 设置选中的节点keys复选框
setCheckedKeys(keys) {
if (this.$refs.treeRef && this.showCheckbox) {
this.$refs.treeRef.setCheckedKeys(keys);
}
},
// 获取选中的节点keys复选框
getCheckedKeys() {
if (this.$refs.treeRef && this.showCheckbox) {
return this.$refs.treeRef.getCheckedKeys();
}
return [];
},
// 获取选中的节点(复选框)
getCheckedNodes() {
if (this.$refs.treeRef && this.showCheckbox) {
return this.$refs.treeRef.getCheckedNodes();
}
return [];
},
// 清空搜索
clearSearch() {
this.searchKeyword = "";
if (this.$refs.treeRef) {
this.$refs.treeRef.filter("");
}
},
// 过滤树
filter(value) {
this.searchKeyword = value;
},
// 开始调整大小
startResize(e) {
e.preventDefault();
e.stopPropagation();
this.isResizing = true;
this.startX = e.type === 'mousedown' ? e.clientX : e.touches[0].clientX;
this.startWidth = this.sidebarWidth;
if (e.type === 'mousedown') {
document.addEventListener('mousemove', this.handleResizeMove);
document.addEventListener('mouseup', this.stopResize);
} else {
document.addEventListener('touchmove', this.handleResizeMove, { passive: false });
document.addEventListener('touchend', this.stopResize);
}
this.disableUserSelect();
},
// 处理调整大小移动
handleResizeMove(e) {
if (!this.isResizing) return;
if (this.rafId) {
cancelAnimationFrame(this.rafId);
}
this.rafId = requestAnimationFrame(() => {
e.preventDefault();
e.stopPropagation();
const clientX = e.type === 'mousemove' ? e.clientX : e.touches[0].clientX;
const deltaX = clientX - this.startX;
const newWidth = this.startWidth + deltaX;
const clampedWidth = Math.max(this.minWidth, Math.min(this.maxWidth, newWidth));
if (Math.abs(clampedWidth - this.sidebarWidth) >= 1) {
this.sidebarWidth = clampedWidth;
}
});
},
// 停止调整大小
stopResize() {
if (!this.isResizing) return;
this.isResizing = false;
if (this.rafId) {
cancelAnimationFrame(this.rafId);
this.rafId = null;
}
this.startX = 0;
this.startWidth = 0;
document.removeEventListener('mousemove', this.handleResizeMove);
document.removeEventListener('mouseup', this.stopResize);
document.removeEventListener('touchmove', this.handleResizeMove);
document.removeEventListener('touchend', this.stopResize);
this.enableUserSelect();
this.saveWidthToStorage();
},
// 禁用用户选择
disableUserSelect() {
document.body.style.userSelect = 'none';
document.body.style.webkitUserSelect = 'none';
document.body.style.mozUserSelect = 'none';
document.body.style.msUserSelect = 'none';
},
// 启用用户选择
enableUserSelect() {
document.body.style.userSelect = '';
document.body.style.webkitUserSelect = '';
document.body.style.mozUserSelect = '';
document.body.style.msUserSelect = '';
},
// 重置宽度到默认值
resetWidth() {
this.sidebarWidth = this.defaultWidth;
this.saveWidthToStorage();
},
// 获取当前宽度
getCurrentWidth() {
return this.sidebarWidth;
},
// 设置宽度
setWidth(width) {
if (typeof width === 'number' && width >= this.minWidth && width <= this.maxWidth) {
this.sidebarWidth = width;
if (!this.collapsed) {
this.saveWidthToStorage();
}
}
}
}
};
</script>
<style lang="scss" scoped>
.tree-sidebar {
flex-shrink: 0;
width: 220px;
background: #fff;
border-right: 1px solid #e8eaed;
display: flex;
flex-direction: column;
overflow: hidden;
position: relative;
transition: width 0.25s ease;
&.collapsed {
width: 42px;
}
&.resizing {
transition: none;
will-change: width;
* {
pointer-events: none !important;
}
}
&.no-initial-transition {
transition: none;
}
}
.resize-handle {
position: absolute;
top: 0;
right: 0;
width: 6px;
height: 100%;
cursor: col-resize;
z-index: 20;
background: transparent;
transition: background 0.2s;
&:hover {
background: rgba(64, 158, 255, 0.3);
}
&.active {
background: rgba(64, 158, 255, 0.5);
}
}
.collapse-button-container {
position: absolute;
top: 50%;
right: 0;
transform: translateY(-50%);
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
width: 15px;
height: 20px;
background: #fff;
border-radius: 0 4px 4px 0;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
transition: all 0.2s ease;
.tree-sidebar.collapsed & {
right: 0;
background: #f7f8fa;
border-radius: 0 4px 4px 0;
}
.tree-sidebar.resizing & {
pointer-events: none;
}
}
.collapse-button {
font-size: 14px;
color: #909399;
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: all 0.2s;
&:hover {
color: #409eff;
background: #ecf5ff;
}
}
.tree-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 10px;
height: 40px;
border-bottom: 1px solid #e8eaed;
background: #f7f8fa;
flex-shrink: 0;
.tree-title {
font-size: 13px;
font-weight: 600;
color: #303133;
white-space: nowrap;
overflow: hidden;
display: flex;
align-items: center;
gap: 5px;
i {
color: #409eff;
font-size: 14px;
}
}
.tree-actions {
display: flex;
align-items: center;
gap: 4px;
flex-shrink: 0;
}
}
.tree-action-icon {
font-size: 14px;
color: #909399;
cursor: pointer;
padding: 4px;
border-radius: 4px;
transition: all 0.2s;
&:hover {
color: #409eff;
background: #ecf5ff;
}
}
.tree-search {
padding: 10px 10px 4px;
flex-shrink: 0;
}
.tree-wrap {
flex: 1;
overflow-y: auto;
padding: 6px 6px 12px;
.tree-sidebar.resizing & {
overflow: hidden;
}
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-thumb {
background: #dcdfe6;
border-radius: 4px;
&:hover {
background: #c0c4cc;
}
}
::v-deep .el-tree-node__content {
height: 32px;
border-radius: 4px;
margin-bottom: 1px;
&:hover {
background: #f0f7ff;
}
}
::v-deep .el-tree-node.is-current > .el-tree-node__content {
background: #e6f0fd;
color: #409eff;
font-weight: 600;
.node-icon {
color: #409eff !important;
}
}
}
.tree-node {
display: flex;
align-items: center;
gap: 5px;
font-size: 13px;
overflow: hidden;
.node-icon {
font-size: 14px;
color: #f5a623;
flex-shrink: 0;
}
.node-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
::v-deep .el-icon-document.node-icon {
color: #909399 !important;
}
</style>

View File

@@ -1,36 +1,36 @@
<template> <template>
<div v-loading="loading" :style="'height:' + height"> <div v-loading="loading" :style="'height:' + height">
<iframe <iframe
:src="src" :src="src"
frameborder="no" frameborder="no"
style="width: 100%; height: 100%" style="width: 100%; height: 100%"
scrolling="auto" scrolling="auto"
/> />
</div> </div>
</template> </template>
<script> <script>
export default { export default {
props: { props: {
src: { src: {
type: String, type: String,
required: true required: true
}, },
}, },
data() { data() {
return { return {
height: document.documentElement.clientHeight - 94.5 + "px;", height: document.documentElement.clientHeight - 94.5 + "px;",
loading: true, loading: true,
url: this.src url: this.src
} }
}, },
mounted: function () { mounted: function () {
setTimeout(() => { setTimeout(() => {
this.loading = false this.loading = false
}, 300) }, 300)
const that = this const that = this
window.onresize = function temp() { window.onresize = function temp() {
that.height = document.documentElement.clientHeight - 94.5 + "px;" that.height = document.documentElement.clientHeight - 94.5 + "px;"
} }
} }
} }
</script> </script>

View File

@@ -1,64 +1,64 @@
/** /**
* v-dialogDrag 弹窗拖拽 * v-dialogDrag 弹窗拖拽
* Copyright (c) 2019 ruoyi * Copyright (c) 2019 ruoyi
*/ */
export default { export default {
bind(el, binding, vnode, oldVnode) { bind(el, binding, vnode, oldVnode) {
const value = binding.value const value = binding.value
if (value == false) return if (value == false) return
// 获取拖拽内容头部 // 获取拖拽内容头部
const dialogHeaderEl = el.querySelector('.el-dialog__header') const dialogHeaderEl = el.querySelector('.el-dialog__header')
const dragDom = el.querySelector('.el-dialog') const dragDom = el.querySelector('.el-dialog')
dialogHeaderEl.style.cursor = 'move' dialogHeaderEl.style.cursor = 'move'
// 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null) // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null)
const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null) const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null)
dragDom.style.position = 'absolute' dragDom.style.position = 'absolute'
dragDom.style.marginTop = 0 dragDom.style.marginTop = 0
let width = dragDom.style.width let width = dragDom.style.width
if (width.includes('%')) { if (width.includes('%')) {
width = +document.body.clientWidth * (+width.replace(/\%/g, '') / 100) width = +document.body.clientWidth * (+width.replace(/\%/g, '') / 100)
} else { } else {
width = +width.replace(/\px/g, '') width = +width.replace(/\px/g, '')
} }
dragDom.style.left = `${(document.body.clientWidth - width) / 2}px` dragDom.style.left = `${(document.body.clientWidth - width) / 2}px`
// 鼠标按下事件 // 鼠标按下事件
dialogHeaderEl.onmousedown = (e) => { dialogHeaderEl.onmousedown = (e) => {
// 鼠标按下,计算当前元素距离可视区的距离 (鼠标点击位置距离可视窗口的距离) // 鼠标按下,计算当前元素距离可视区的距离 (鼠标点击位置距离可视窗口的距离)
const disX = e.clientX - dialogHeaderEl.offsetLeft const disX = e.clientX - dialogHeaderEl.offsetLeft
const disY = e.clientY - dialogHeaderEl.offsetTop const disY = e.clientY - dialogHeaderEl.offsetTop
// 获取到的值带px 正则匹配替换 // 获取到的值带px 正则匹配替换
let styL, styT let styL, styT
// 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
if (sty.left.includes('%')) { if (sty.left.includes('%')) {
styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100) styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100)
styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100) styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100)
} else { } else {
styL = +sty.left.replace(/\px/g, '') styL = +sty.left.replace(/\px/g, '')
styT = +sty.top.replace(/\px/g, '') styT = +sty.top.replace(/\px/g, '')
} }
// 鼠标拖拽事件 // 鼠标拖拽事件
document.onmousemove = function (e) { document.onmousemove = function (e) {
// 通过事件委托,计算移动的距离 (开始拖拽至结束拖拽的距离) // 通过事件委托,计算移动的距离 (开始拖拽至结束拖拽的距离)
const l = e.clientX - disX const l = e.clientX - disX
const t = e.clientY - disY const t = e.clientY - disY
let finallyL = l + styL let finallyL = l + styL
let finallyT = t + styT let finallyT = t + styT
// 移动当前元素 // 移动当前元素
dragDom.style.left = `${finallyL}px` dragDom.style.left = `${finallyL}px`
dragDom.style.top = `${finallyT}px` dragDom.style.top = `${finallyT}px`
} }
document.onmouseup = function (e) { document.onmouseup = function (e) {
document.onmousemove = null document.onmousemove = null
document.onmouseup = null document.onmouseup = null
} }
} }
} }
} }

View File

@@ -1,34 +1,34 @@
/** /**
* v-dialogDragWidth 可拖动弹窗高度(右下角) * v-dialogDragWidth 可拖动弹窗高度(右下角)
* Copyright (c) 2019 ruoyi * Copyright (c) 2019 ruoyi
*/ */
export default { export default {
bind(el) { bind(el) {
const dragDom = el.querySelector('.el-dialog') const dragDom = el.querySelector('.el-dialog')
const lineEl = document.createElement('div') const lineEl = document.createElement('div')
lineEl.style = 'width: 6px; background: inherit; height: 10px; position: absolute; right: 0; bottom: 0; margin: auto; z-index: 1; cursor: nwse-resize;' lineEl.style = 'width: 6px; background: inherit; height: 10px; position: absolute; right: 0; bottom: 0; margin: auto; z-index: 1; cursor: nwse-resize;'
lineEl.addEventListener('mousedown', lineEl.addEventListener('mousedown',
function(e) { function(e) {
// 鼠标按下,计算当前元素距离可视区的距离 // 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX - el.offsetLeft const disX = e.clientX - el.offsetLeft
const disY = e.clientY - el.offsetTop const disY = e.clientY - el.offsetTop
// 当前宽度 高度 // 当前宽度 高度
const curWidth = dragDom.offsetWidth const curWidth = dragDom.offsetWidth
const curHeight = dragDom.offsetHeight const curHeight = dragDom.offsetHeight
document.onmousemove = function(e) { document.onmousemove = function(e) {
e.preventDefault() // 移动时禁用默认事件 e.preventDefault() // 移动时禁用默认事件
// 通过事件委托,计算移动的距离 // 通过事件委托,计算移动的距离
const xl = e.clientX - disX const xl = e.clientX - disX
const yl = e.clientY - disY const yl = e.clientY - disY
dragDom.style.width = `${curWidth + xl}px` dragDom.style.width = `${curWidth + xl}px`
dragDom.style.height = `${curHeight + yl}px` dragDom.style.height = `${curHeight + yl}px`
} }
document.onmouseup = function(e) { document.onmouseup = function(e) {
document.onmousemove = null document.onmousemove = null
document.onmouseup = null document.onmouseup = null
} }
}, false) }, false)
dragDom.appendChild(lineEl) dragDom.appendChild(lineEl)
} }
} }

View File

@@ -1,30 +1,30 @@
/** /**
* v-dialogDragWidth 可拖动弹窗宽度(右侧边) * v-dialogDragWidth 可拖动弹窗宽度(右侧边)
* Copyright (c) 2019 ruoyi * Copyright (c) 2019 ruoyi
*/ */
export default { export default {
bind(el) { bind(el) {
const dragDom = el.querySelector('.el-dialog') const dragDom = el.querySelector('.el-dialog')
const lineEl = document.createElement('div') const lineEl = document.createElement('div')
lineEl.style = 'width: 5px; background: inherit; height: 80%; position: absolute; right: 0; top: 0; bottom: 0; margin: auto; z-index: 1; cursor: w-resize;' lineEl.style = 'width: 5px; background: inherit; height: 80%; position: absolute; right: 0; top: 0; bottom: 0; margin: auto; z-index: 1; cursor: w-resize;'
lineEl.addEventListener('mousedown', lineEl.addEventListener('mousedown',
function (e) { function (e) {
// 鼠标按下,计算当前元素距离可视区的距离 // 鼠标按下,计算当前元素距离可视区的距离
const disX = e.clientX - el.offsetLeft const disX = e.clientX - el.offsetLeft
// 当前宽度 // 当前宽度
const curWidth = dragDom.offsetWidth const curWidth = dragDom.offsetWidth
document.onmousemove = function (e) { document.onmousemove = function (e) {
e.preventDefault() // 移动时禁用默认事件 e.preventDefault() // 移动时禁用默认事件
// 通过事件委托,计算移动的距离 // 通过事件委托,计算移动的距离
const l = e.clientX - disX const l = e.clientX - disX
dragDom.style.width = `${curWidth + l}px` dragDom.style.width = `${curWidth + l}px`
} }
document.onmouseup = function (e) { document.onmouseup = function (e) {
document.onmousemove = null document.onmousemove = null
document.onmouseup = null document.onmouseup = null
} }
}, false) }, false)
dragDom.appendChild(lineEl) dragDom.appendChild(lineEl)
} }
} }

View File

@@ -1,23 +1,23 @@
import hasRole from './permission/hasRole' import hasRole from './permission/hasRole'
import hasPermi from './permission/hasPermi' import hasPermi from './permission/hasPermi'
import dialogDrag from './dialog/drag' import dialogDrag from './dialog/drag'
import dialogDragWidth from './dialog/dragWidth' import dialogDragWidth from './dialog/dragWidth'
import dialogDragHeight from './dialog/dragHeight' import dialogDragHeight from './dialog/dragHeight'
import clipboard from './module/clipboard' import clipboard from './module/clipboard'
const install = function(Vue) { const install = function(Vue) {
Vue.directive('hasRole', hasRole) Vue.directive('hasRole', hasRole)
Vue.directive('hasPermi', hasPermi) Vue.directive('hasPermi', hasPermi)
Vue.directive('clipboard', clipboard) Vue.directive('clipboard', clipboard)
Vue.directive('dialogDrag', dialogDrag) Vue.directive('dialogDrag', dialogDrag)
Vue.directive('dialogDragWidth', dialogDragWidth) Vue.directive('dialogDragWidth', dialogDragWidth)
Vue.directive('dialogDragHeight', dialogDragHeight) Vue.directive('dialogDragHeight', dialogDragHeight)
} }
if (window.Vue) { if (window.Vue) {
window['hasRole'] = hasRole window['hasRole'] = hasRole
window['hasPermi'] = hasPermi window['hasPermi'] = hasPermi
Vue.use(install) Vue.use(install)
} }
export default install export default install

View File

@@ -1,28 +1,28 @@
/** /**
* v-hasPermi 操作权限处理 * v-hasPermi 操作权限处理
* Copyright (c) 2019 ruoyi * Copyright (c) 2019 ruoyi
*/ */
import store from '@/store' import store from '@/store'
export default { export default {
inserted(el, binding, vnode) { inserted(el, binding, vnode) {
const { value } = binding const { value } = binding
const all_permission = "*:*:*" const all_permission = "*:*:*"
const permissions = store.getters && store.getters.permissions const permissions = store.getters && store.getters.permissions
if (value && value instanceof Array && value.length > 0) { if (value && value instanceof Array && value.length > 0) {
const permissionFlag = value const permissionFlag = value
const hasPermissions = permissions.some(permission => { const hasPermissions = permissions.some(permission => {
return all_permission === permission || permissionFlag.includes(permission) return all_permission === permission || permissionFlag.includes(permission)
}) })
if (!hasPermissions) { if (!hasPermissions) {
el.parentNode && el.parentNode.removeChild(el) el.parentNode && el.parentNode.removeChild(el)
} }
} else { } else {
throw new Error(`请设置操作权限标签值`) throw new Error(`请设置操作权限标签值`)
} }
} }
} }

View File

@@ -1,28 +1,28 @@
/** /**
* v-hasRole 角色权限处理 * v-hasRole 角色权限处理
* Copyright (c) 2019 ruoyi * Copyright (c) 2019 ruoyi
*/ */
import store from '@/store' import store from '@/store'
export default { export default {
inserted(el, binding, vnode) { inserted(el, binding, vnode) {
const { value } = binding const { value } = binding
const super_admin = "admin" const super_admin = "admin"
const roles = store.getters && store.getters.roles const roles = store.getters && store.getters.roles
if (value && value instanceof Array && value.length > 0) { if (value && value instanceof Array && value.length > 0) {
const roleFlag = value const roleFlag = value
const hasRole = roles.some(role => { const hasRole = roles.some(role => {
return super_admin === role || roleFlag.includes(role) return super_admin === role || roleFlag.includes(role)
}) })
if (!hasRole) { if (!hasRole) {
el.parentNode && el.parentNode.removeChild(el) el.parentNode && el.parentNode.removeChild(el)
} }
} else { } else {
throw new Error(`请设置角色权限标签值"`) throw new Error(`请设置角色权限标签值"`)
} }
} }
} }

View File

@@ -51,14 +51,6 @@ export default {
width: 100%; width: 100%;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
&:fullscreen,
&:-webkit-full-screen,
&:-moz-full-screen,
&:-ms-fullscreen {
background: #fff;
overflow-y: auto;
}
} }
.fixed-header + .app-main { .fixed-header + .app-main {
@@ -137,4 +129,4 @@ export default {
background-color: #c0c0c0; background-color: #c0c0c0;
border-radius: 3px; border-radius: 3px;
} }
</style> </style>

View File

@@ -1,362 +0,0 @@
<template>
<el-drawer title="公告详情" :visible.sync="visible" direction="rtl" size="50%" append-to-body :before-close="handleClose" custom-class="notice-detail-drawer">
<div v-loading="loading" class="notice-detail-drawer__body">
<div v-if="!detail" class="notice-empty">
<i class="el-icon-document"></i>
<span>暂无数据</span>
</div>
<div v-else class="notice-page">
<div class="notice-type-wrap">
<span v-if="detail.noticeType === '1'" class="notice-type-tag type-notify">
<i class="el-icon-bell"></i> 通知
</span>
<span v-else-if="detail.noticeType === '2'" class="notice-type-tag type-announce">
<i class="el-icon-message"></i> 公告
</span>
<span v-else class="notice-type-tag type-notify">
<i class="el-icon-document"></i> 消息
</span>
</div>
<h1 class="notice-title">{{ detail.noticeTitle }}</h1>
<div class="notice-meta">
<span class="meta-item">
<i class="el-icon-user"></i>
<span>{{ detail.createBy || '—' }}</span>
</span>
<span class="meta-item">
<i class="el-icon-time"></i>
<span>{{ detail.createTime || '—' }}</span>
</span>
<span class="meta-item">
<span :class="['status-dot', isStatusNormal ? 'status-ok' : 'status-off']"></span>
<span>{{ isStatusNormal ? '正常' : '已关闭' }}</span>
</span>
</div>
<div class="notice-divider">
<span class="notice-divider-dot"></span>
<span class="notice-divider-dot"></span>
<span class="notice-divider-dot"></span>
</div>
<div class="notice-body">
<div v-if="hasContent" class="notice-content" v-html="detail.noticeContent" />
<div v-else class="notice-empty notice-empty--inner">
<i class="el-icon-document"></i> 暂无内容
</div>
</div>
</div>
</div>
</el-drawer>
</template>
<script>
import { getNotice } from '@/api/system/notice'
export default {
name: 'NoticeDetailView',
data() {
return {
visible: false,
loading: false,
detail: null
}
},
computed: {
isStatusNormal() {
const s = this.detail && this.detail.status
return s === '0' || s === 0
},
hasContent() {
const c = this.detail && this.detail.noticeContent
return c != null && String(c).trim() !== ''
}
},
methods: {
open(payload) {
let id = null
let preset = null
if (payload != null && typeof payload === 'object') {
id = payload.noticeId
if (payload.noticeContent != null) {
preset = payload
}
} else {
id = payload
}
this.visible = true
if (preset) {
this.detail = preset
return
}
if (id == null || id === '') {
this.detail = null
return
}
this.loading = true
this.detail = null
getNotice(id).then(res => {
this.detail = res.data
}).catch(() => {
this.detail = null
}).finally(() => {
this.loading = false
})
},
handleClose() {
this.visible = false
this.detail = null
this.loading = false
}
}
}
</script>
<style lang="scss" scoped>
.notice-page {
max-width: 760px;
margin: 0 auto;
padding: 8px 8px 20px;
animation: notice-fade-up 0.28s ease both;
}
@keyframes notice-fade-up {
from {
opacity: 0;
transform: translateY(14px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.notice-type-tag {
display: inline-flex;
align-items: center;
gap: 5px;
padding: 3px 12px;
border-radius: 2px;
font-size: 11px;
font-weight: 700;
letter-spacing: 1px;
text-transform: uppercase;
margin-bottom: 14px;
}
.type-notify {
background: #fff8e6;
color: #b7791f;
border-left: 3px solid #d97706;
}
.type-announce {
background: #e8f5e9;
color: #276749;
border-left: 3px solid #38a169;
}
.notice-title {
font-size: 22px;
font-weight: 700;
color: #1a202c;
line-height: 1.45;
margin: 0 0 16px;
letter-spacing: -0.2px;
}
.notice-meta {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 16px;
padding: 12px 0;
border-top: 1px solid #e9ecef;
border-bottom: 1px solid #e9ecef;
margin-bottom: 28px;
}
.meta-item {
display: flex;
align-items: center;
gap: 5px;
font-size: 12px;
color: #718096;
}
.meta-item i {
font-size: 12px;
color: #a0aec0;
}
.status-dot {
display: inline-block;
width: 7px;
height: 7px;
border-radius: 50%;
margin-right: 4px;
}
.status-ok {
background: #38a169;
}
.status-off {
background: #e53e3e;
}
.notice-divider {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 24px;
}
.notice-divider::before,
.notice-divider::after {
content: '';
flex: 1;
height: 1px;
background: linear-gradient(to right, transparent, #dee2e6, transparent);
}
.notice-divider-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #cbd5e0;
}
.notice-body {
background: #fff;
border-radius: 6px;
padding: 28px 32px;
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(0, 0, 0, 0.04);
min-height: 120px;
}
.notice-content {
font-size: 14px;
line-height: 1.85;
color: #2d3748;
word-break: break-word;
}
.notice-content ::v-deep p {
margin: 0 0 1em;
}
.notice-content ::v-deep h1,
.notice-content ::v-deep h2,
.notice-content ::v-deep h3 {
font-weight: 700;
color: #1a202c;
margin: 1.4em 0 0.6em;
}
.notice-content ::v-deep h1 {
font-size: 18px;
}
.notice-content ::v-deep h2 {
font-size: 16px;
}
.notice-content ::v-deep h3 {
font-size: 14px;
}
.notice-content ::v-deep a {
color: #3182ce;
text-decoration: underline;
}
.notice-content ::v-deep a:hover {
color: #2b6cb0;
}
.notice-content ::v-deep img {
max-width: 100%;
border-radius: 4px;
margin: 8px 0;
}
.notice-content ::v-deep ul,
.notice-content ::v-deep ol {
padding-left: 20px;
margin: 0 0 1em;
}
.notice-content ::v-deep li {
margin-bottom: 4px;
}
.notice-content ::v-deep blockquote {
border-left: 3px solid #cbd5e0;
margin: 1em 0;
padding: 6px 16px;
color: #718096;
background: #f7fafc;
}
.notice-content ::v-deep table {
border-collapse: collapse;
width: 100%;
margin: 1em 0;
font-size: 13px;
}
.notice-content ::v-deep table th,
.notice-content ::v-deep table td {
border: 1px solid #e2e8f0;
padding: 7px 12px;
}
.notice-content ::v-deep table th {
background: #f7fafc;
font-weight: 600;
}
.notice-empty {
text-align: center;
padding: 40px 0;
color: #a0aec0;
font-size: 13px;
}
.notice-empty i {
font-size: 28px;
display: block;
margin-bottom: 10px;
}
.notice-empty--inner {
padding: 32px 0;
}
.notice-empty--inner i {
font-size: 28px;
}
::v-deep .notice-detail-drawer {
.el-drawer__header {
margin-bottom: 0;
padding: 16px 20px;
border-bottom: 1px solid #ebeef5;
font-size: 16px;
font-weight: 600;
color: #303133;
}
.el-drawer__body {
background: #f5f6f8;
}
}
.notice-detail-drawer__body {
height: 100%;
overflow: auto;
padding: 10px 16px 22px;
}
</style>

View File

@@ -1,181 +0,0 @@
<template>
<div>
<el-popover ref="noticePopover" placement="bottom-end" width="320" trigger="manual" :value="noticeVisible" popper-class="notice-popover">
<div class="notice-header">
<span class="notice-title">通知公告</span>
<span class="notice-mark-all" @click="markAllRead">全部已读</span>
</div>
<div v-if="noticeLoading" class="notice-loading"><i class="el-icon-loading"></i> 加载中...</div>
<div v-else-if="noticeList.length === 0" class="notice-empty"><i class="el-icon-inbox"></i><br>暂无公告</div>
<div v-else>
<div v-for="item in noticeList" :key="item.noticeId" class="notice-item" :class="{ 'is-read': item.isRead }" @click="previewNotice(item)">
<el-tag size="mini" :type="item.noticeType === '1' ? 'warning' : 'success'" class="notice-tag">
{{ item.noticeType === '1' ? '通知' : '公告' }}
</el-tag>
<span class="notice-item-title">{{ item.noticeTitle }}</span>
<span class="notice-item-date">{{ item.createTime }}</span>
</div>
</div>
</el-popover>
<div v-popover:noticePopover class="right-menu-item hover-effect notice-trigger" @mouseenter="onNoticeEnter" @mouseleave="onNoticeLeave">
<svg-icon icon-class="bell" />
<span v-if="unreadCount > 0" class="notice-badge">{{ unreadCount }}</span>
</div>
<notice-detail-view ref="noticeViewRef" />
</div>
</template>
<script>
import NoticeDetailView from './DetailView'
import { listNoticeTop, markNoticeRead, markNoticeReadAll } from '@/api/system/notice'
export default {
name: 'HeaderNotice',
components: { NoticeDetailView },
data() {
return {
noticeList: [], // 通知列表
unreadCount: 0, // 未读数量
noticeLoading: false, // 加载状态
noticeVisible: false, // 弹出层显示状态
noticeLeaveTimer: null // 鼠标离开计时器
}
},
mounted() {
this.loadNoticeTop()
},
methods: {
// 鼠标移入铃铛区域
onNoticeEnter() {
clearTimeout(this.noticeLeaveTimer)
this.noticeVisible = true
this.$nextTick(() => {
const popper = this.$refs.noticePopover.$refs.popper
if (popper && !popper._noticeBound) {
popper._noticeBound = true
popper.addEventListener('mouseenter', () => clearTimeout(this.noticeLeaveTimer))
popper.addEventListener('mouseleave', () => {
this.noticeLeaveTimer = setTimeout(() => { this.noticeVisible = false }, 100)
})
}
})
},
// 鼠标离开铃铛区域
onNoticeLeave() {
this.noticeLeaveTimer = setTimeout(() => { this.noticeVisible = false }, 150)
},
// 加载顶部公告列表
loadNoticeTop() {
this.noticeLoading = true
listNoticeTop().then(res => {
this.noticeList = res.data || []
this.unreadCount = res.unreadCount !== undefined ? res.unreadCount : this.noticeList.filter(n => !n.isRead).length
}).finally(() => {
this.noticeLoading = false
})
},
// 预览公告详情
previewNotice(item) {
if (!item.isRead) {
markNoticeRead(item.noticeId).catch(() => {})
item.isRead = true
const idx = this.noticeList.indexOf(item)
if (idx !== -1) this.$set(this.noticeList, idx, { ...item, isRead: true })
this.unreadCount = Math.max(0, this.unreadCount - 1)
}
this.$refs.noticeViewRef.open(item.noticeId)
},
// 全部已读
markAllRead() {
const ids = this.noticeList.map(n => n.noticeId).join(',')
if (!ids) return
markNoticeReadAll(ids).catch(() => {})
this.noticeList = this.noticeList.map(n => ({ ...n, isRead: true }))
this.unreadCount = 0
}
}
}
</script>
<style lang="scss" scoped>
.notice-trigger {
position: relative;
transform: translateX(-6px);
.svg-icon { width: 1.2em; height: 1.2em; vertical-align: -0.2em; }
.notice-badge {
position: absolute;
top: 7px;
right: -3px;
background: #f56c6c;
color: #fff;
border-radius: 10px;
font-size: 10px;
height: 16px;
line-height: 16px;
padding: 0 4px;
min-width: 16px;
text-align: center;
white-space: nowrap;
pointer-events: none;
}
}
.notice-popover {
padding: 0 !important;
}
.notice-popover .notice-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 14px;
background: #f7f9fb;
border-bottom: 1px solid #eee;
font-size: 13px;
font-weight: 600;
color: #333;
}
.notice-popover .notice-mark-all {
font-size: 12px;
color: #409EFF;
font-weight: normal;
cursor: pointer;
}
.notice-popover .notice-mark-all:hover { color: #2b7cc1; }
.notice-popover .notice-loading,
.notice-popover .notice-empty {
padding: 24px;
text-align: center;
color: #bbb;
font-size: 12px;
line-height: 1.8;
}
.notice-popover .notice-item {
display: flex;
align-items: center;
gap: 8px;
padding: 10px 14px;
border-bottom: 1px solid #f5f5f5;
cursor: pointer;
transition: background 0.15s;
}
.notice-popover .notice-item:last-child { border-bottom: none; }
.notice-popover .notice-item:hover { background: #f7f9fb; }
.notice-popover .notice-item.is-read .notice-tag,
.notice-popover .notice-item.is-read .notice-item-title,
.notice-popover .notice-item.is-read .notice-item-date { opacity: 0.45; filter: grayscale(1); color: #999; }
.notice-popover .notice-tag { flex-shrink: 0; }
.notice-popover .notice-item-title {
flex: 1;
font-size: 12px;
color: #333;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.notice-popover .notice-item-date {
flex-shrink: 0;
font-size: 11px;
color: #bbb;
}
</style>

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