Compare commits
10 Commits
65159934ab
...
6e6eaa14af
Author | SHA1 | Date | |
---|---|---|---|
6e6eaa14af | |||
e61580d330 | |||
53c10570b6 | |||
![]() |
725c7dcea2 | ||
![]() |
158ccaebe0 | ||
![]() |
7b9060af26 | ||
![]() |
1a2f20e859 | ||
![]() |
09faecb5d3 | ||
![]() |
d46e62a21a | ||
![]() |
fa88922637 |
@ -1,11 +1,11 @@
|
||||
<p align="center">
|
||||
<img alt="logo" src="https://oscimg.oschina.net/oscnet/up-d3d0a9303e11d522a06cd263f3079027715.png">
|
||||
</p>
|
||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.8.9</h1>
|
||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">RuoYi v3.9.0</h1>
|
||||
<h4 align="center">基于SpringBoot+Vue前后端分离的Java快速开发框架</h4>
|
||||
<p align="center">
|
||||
<a href="https://gitee.com/y_project/RuoYi-Vue/stargazers"><img src="https://gitee.com/y_project/RuoYi-Vue/badge/star.svg?theme=dark"></a>
|
||||
<a href="https://gitee.com/y_project/RuoYi-Vue"><img src="https://img.shields.io/badge/RuoYi-v3.8.9-brightgreen.svg"></a>
|
||||
<a href="https://gitee.com/y_project/RuoYi-Vue"><img src="https://img.shields.io/badge/RuoYi-v3.9.0-brightgreen.svg"></a>
|
||||
<a href="https://gitee.com/y_project/RuoYi-Vue/blob/master/LICENSE"><img src="https://img.shields.io/github/license/mashape/apistatus.svg"></a>
|
||||
</p>
|
||||
|
||||
@ -92,4 +92,4 @@
|
||||
|
||||
## 若依前后端分离交流群
|
||||
|
||||
QQ群: [](https://jq.qq.com/?_wv=1027&k=5bVB1og) [](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [](https://jq.qq.com/?_wv=1027&k=51G72yr) [](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) [](https://jq.qq.com/?_wv=1027&k=EI9an8lJ) [](https://jq.qq.com/?_wv=1027&k=SWCtLnMz) [](https://jq.qq.com/?_wv=1027&k=96Dkdq0k) [](https://jq.qq.com/?_wv=1027&k=0fsNiYZt) [](https://jq.qq.com/?_wv=1027&k=7xw4xUG1) [](https://jq.qq.com/?_wv=1027&k=eCx8eyoJ) [](https://jq.qq.com/?_wv=1027&k=SpyH2875) [](https://jq.qq.com/?_wv=1027&k=tKEt51dz) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=0vBbSb0ztbBgVtn3kJS-Q4HUNYwip89G&authKey=8irq5PhutrZmWIvsUsklBxhj57l%2F1nOZqjzigkXZVoZE451GG4JHPOqW7AW6cf0T&noverify=0&group_code=143961921) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=ZFAPAbp09S2ltvwrJzp7wGlbopsc0rwi&authKey=HB2cxpxP2yspk%2Bo3WKTBfktRCccVkU26cgi5B16u0KcAYrVu7sBaE7XSEqmMdFQp&noverify=0&group_code=174951577) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Fn2aF5IHpwsy8j6VlalNJK6qbwFLFHat&authKey=uyIT%2B97x2AXj3odyXpsSpVaPMC%2Bidw0LxG5MAtEqlrcBcWJUA%2FeS43rsF1Tg7IRJ&noverify=0&group_code=161281055) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=XIzkm_mV2xTsUtFxo63bmicYoDBA6Ifm&authKey=dDW%2F4qsmw3x9govoZY9w%2FoWAoC4wbHqGal%2BbqLzoS6VBarU8EBptIgPKN%2FviyC8j&noverify=0&group_code=138988063) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=DkugnCg68PevlycJSKSwjhFqfIgrWWwR&authKey=pR1Pa5lPIeGF%2FFtIk6d%2FGB5qFi0EdvyErtpQXULzo03zbhopBHLWcuqdpwY241R%2F&noverify=0&group_code=151450850) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=F58bgRa-Dp-rsQJThiJqIYv8t4-lWfXh&authKey=UmUs4CVG5OPA1whvsa4uSespOvyd8%2FAr9olEGaWAfdLmfKQk%2FVBp2YU3u2xXXt76&noverify=0&group_code=224622315) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Nxb2EQ5qozWa218Wbs7zgBnjLSNk_tVT&authKey=obBKXj6SBKgrFTJZx0AqQnIYbNOvBB2kmgwWvGhzxR67RoRr84%2Bus5OadzMcdJl5&noverify=0&group_code=287842588) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=numtK1M_I4eVd2Gvg8qtbuL8JgX42qNh&authKey=giV9XWMaFZTY%2FqPlmWbkB9g3fi0Ev5CwEtT9Tgei0oUlFFCQLDp4ozWRiVIzubIm&noverify=0&group_code=187944233) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=G6r5KGCaa3pqdbUSXNIgYloyb8e0_L0D&authKey=4w8tF1eGW7%2FedWn%2FHAypQksdrML%2BDHolQSx7094Agm7Luakj9EbfPnSTxSi2T1LQ&noverify=0&group_code=228578329) 点击按钮入群。
|
||||
QQ群: [](https://jq.qq.com/?_wv=1027&k=5bVB1og) [](https://jq.qq.com/?_wv=1027&k=5eiA4DH) [](https://jq.qq.com/?_wv=1027&k=5AxMKlC) [](https://jq.qq.com/?_wv=1027&k=51G72yr) [](https://jq.qq.com/?_wv=1027&k=VvjN2nvu) [](https://jq.qq.com/?_wv=1027&k=5vYAqA05) [](https://jq.qq.com/?_wv=1027&k=kOIINEb5) [](https://jq.qq.com/?_wv=1027&k=UKtX5jhs) [](https://jq.qq.com/?_wv=1027&k=EI9an8lJ) [](https://jq.qq.com/?_wv=1027&k=SWCtLnMz) [](https://jq.qq.com/?_wv=1027&k=96Dkdq0k) [](https://jq.qq.com/?_wv=1027&k=0fsNiYZt) [](https://jq.qq.com/?_wv=1027&k=7xw4xUG1) [](https://jq.qq.com/?_wv=1027&k=eCx8eyoJ) [](https://jq.qq.com/?_wv=1027&k=SpyH2875) [](https://jq.qq.com/?_wv=1027&k=tKEt51dz) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=0vBbSb0ztbBgVtn3kJS-Q4HUNYwip89G&authKey=8irq5PhutrZmWIvsUsklBxhj57l%2F1nOZqjzigkXZVoZE451GG4JHPOqW7AW6cf0T&noverify=0&group_code=143961921) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=ZFAPAbp09S2ltvwrJzp7wGlbopsc0rwi&authKey=HB2cxpxP2yspk%2Bo3WKTBfktRCccVkU26cgi5B16u0KcAYrVu7sBaE7XSEqmMdFQp&noverify=0&group_code=174951577) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Fn2aF5IHpwsy8j6VlalNJK6qbwFLFHat&authKey=uyIT%2B97x2AXj3odyXpsSpVaPMC%2Bidw0LxG5MAtEqlrcBcWJUA%2FeS43rsF1Tg7IRJ&noverify=0&group_code=161281055) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=XIzkm_mV2xTsUtFxo63bmicYoDBA6Ifm&authKey=dDW%2F4qsmw3x9govoZY9w%2FoWAoC4wbHqGal%2BbqLzoS6VBarU8EBptIgPKN%2FviyC8j&noverify=0&group_code=138988063) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=DkugnCg68PevlycJSKSwjhFqfIgrWWwR&authKey=pR1Pa5lPIeGF%2FFtIk6d%2FGB5qFi0EdvyErtpQXULzo03zbhopBHLWcuqdpwY241R%2F&noverify=0&group_code=151450850) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=F58bgRa-Dp-rsQJThiJqIYv8t4-lWfXh&authKey=UmUs4CVG5OPA1whvsa4uSespOvyd8%2FAr9olEGaWAfdLmfKQk%2FVBp2YU3u2xXXt76&noverify=0&group_code=224622315) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=Nxb2EQ5qozWa218Wbs7zgBnjLSNk_tVT&authKey=obBKXj6SBKgrFTJZx0AqQnIYbNOvBB2kmgwWvGhzxR67RoRr84%2Bus5OadzMcdJl5&noverify=0&group_code=287842588) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=numtK1M_I4eVd2Gvg8qtbuL8JgX42qNh&authKey=giV9XWMaFZTY%2FqPlmWbkB9g3fi0Ev5CwEtT9Tgei0oUlFFCQLDp4ozWRiVIzubIm&noverify=0&group_code=187944233) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=G6r5KGCaa3pqdbUSXNIgYloyb8e0_L0D&authKey=4w8tF1eGW7%2FedWn%2FHAypQksdrML%2BDHolQSx7094Agm7Luakj9EbfPnSTxSi2T1LQ&noverify=0&group_code=228578329) [](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=GsOo-OLz53J8y_9TPoO6XXSGNRTgbFxA&authKey=R7Uy%2Feq%2BZsoKNqHvRKhiXpypW7DAogoWapOawUGHokJSBIBIre2%2FoiAZeZBSLuBc&noverify=0&group_code=191164766) 点击按钮入群。
|
Binary file not shown.
8
pom.xml
8
pom.xml
@ -6,14 +6,14 @@
|
||||
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi</artifactId>
|
||||
<version>3.8.9</version>
|
||||
<version>3.9.0</version>
|
||||
|
||||
<name>ruoyi</name>
|
||||
<url>http://www.ruoyi.vip</url>
|
||||
<description>若依管理系统</description>
|
||||
|
||||
<properties>
|
||||
<ruoyi.version>3.8.9</ruoyi.version>
|
||||
<ruoyi.version>3.9.0</ruoyi.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
@ -25,13 +25,13 @@
|
||||
<kaptcha.version>2.3.3</kaptcha.version>
|
||||
<pagehelper.boot.version>1.4.7</pagehelper.boot.version>
|
||||
<fastjson.version>2.0.57</fastjson.version>
|
||||
<oshi.version>6.8.1</oshi.version>
|
||||
<oshi.version>6.8.2</oshi.version>
|
||||
<commons.io.version>2.19.0</commons.io.version>
|
||||
<poi.version>4.1.2</poi.version>
|
||||
<velocity.version>2.3</velocity.version>
|
||||
<jwt.version>0.9.1</jwt.version>
|
||||
<!-- override dependency version -->
|
||||
<tomcat.version>9.0.105</tomcat.version>
|
||||
<tomcat.version>9.0.106</tomcat.version>
|
||||
<logback.version>1.2.13</logback.version>
|
||||
<spring-security.version>5.7.12</spring-security.version>
|
||||
<spring-framework.version>5.3.39</spring-framework.version>
|
||||
|
@ -1,96 +1,107 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>ruoyi</artifactId>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<version>3.8.9</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
<artifactId>ruoyi-admin</artifactId>
|
||||
|
||||
<description>
|
||||
web服务入口
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- spring-boot-devtools -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<optional>true</optional> <!-- 表示依赖不会传递 -->
|
||||
</dependency>
|
||||
|
||||
<!-- swagger3-->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 防止进入swagger页面报类型转换错误,排除3.0.0中的引用,手动增加1.6.2版本 -->
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-models</artifactId>
|
||||
<version>1.6.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Mysql驱动包 -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 核心模块-->
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-framework</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 定时任务-->
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-quartz</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 代码生成-->
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-generator</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>2.5.15</version>
|
||||
<configuration>
|
||||
<fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-war-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<configuration>
|
||||
<failOnMissingWebXml>false</failOnMissingWebXml>
|
||||
<warName>${project.artifactId}</warName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
</build>
|
||||
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>ruoyi</artifactId>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<version>3.9.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<packaging>jar</packaging>
|
||||
<artifactId>ruoyi-admin</artifactId>
|
||||
|
||||
<description>
|
||||
web服务入口
|
||||
</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- spring-boot-devtools -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<optional>true</optional> <!-- 表示依赖不会传递 -->
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/us.codecraft/webmagic-core -->
|
||||
<dependency>
|
||||
<groupId>us.codecraft</groupId>
|
||||
<artifactId>webmagic-core</artifactId>
|
||||
<version>1.0.3</version>
|
||||
</dependency>
|
||||
<!-- https://mvnrepository.com/artifact/us.codecraft/webmagic-extension -->
|
||||
<dependency>
|
||||
<groupId>us.codecraft</groupId>
|
||||
<artifactId>webmagic-extension</artifactId>
|
||||
<version>1.0.3</version>
|
||||
</dependency>
|
||||
<!-- swagger3-->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-boot-starter</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 防止进入swagger页面报类型转换错误,排除3.0.0中的引用,手动增加1.6.2版本 -->
|
||||
<dependency>
|
||||
<groupId>io.swagger</groupId>
|
||||
<artifactId>swagger-models</artifactId>
|
||||
<version>1.6.2</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Mysql驱动包 -->
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 核心模块-->
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-framework</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 定时任务-->
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-quartz</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 代码生成-->
|
||||
<dependency>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<artifactId>ruoyi-generator</artifactId>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<version>2.5.15</version>
|
||||
<configuration>
|
||||
<fork>true</fork> <!-- 如果没有该配置,devtools不会生效 -->
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<goals>
|
||||
<goal>repackage</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-war-plugin</artifactId>
|
||||
<version>3.1.0</version>
|
||||
<configuration>
|
||||
<failOnMissingWebXml>false</failOnMissingWebXml>
|
||||
<warName>${project.artifactId}</warName>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
<finalName>${project.artifactId}</finalName>
|
||||
</build>
|
||||
|
||||
</project>
|
@ -0,0 +1,22 @@
|
||||
package com.ruoyi.framework.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* 程序注解配置
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Configuration
|
||||
public class BeanRestConfig
|
||||
{
|
||||
/**
|
||||
* 创建RestTemplate Bean
|
||||
*/
|
||||
@Bean
|
||||
public RestTemplate restTemplate() {
|
||||
return new RestTemplate();
|
||||
}
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
package com.ruoyi.web.controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
/**
|
||||
* prodController
|
||||
*
|
||||
* @author ruoyi
|
||||
* @date 2025-06-21
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/prod/products")
|
||||
public class ProductsController extends BaseController
|
||||
{
|
||||
|
||||
|
||||
}
|
@ -14,7 +14,6 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import com.ruoyi.common.config.RuoYiConfig;
|
||||
import com.ruoyi.common.constant.Constants;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.file.FileUploadUtils;
|
||||
@ -148,7 +147,7 @@ public class CommonController
|
||||
// 本地资源路径
|
||||
String localPath = RuoYiConfig.getProfile();
|
||||
// 数据库资源地址
|
||||
String downloadPath = localPath + StringUtils.substringAfter(resource, Constants.RESOURCE_PREFIX);
|
||||
String downloadPath = localPath + FileUtils.stripPrefix(resource);
|
||||
// 下载名称
|
||||
String downloadName = StringUtils.substringAfterLast(downloadPath, "/");
|
||||
response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);
|
||||
|
@ -21,6 +21,7 @@ import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.SecurityUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.file.FileUploadUtils;
|
||||
import com.ruoyi.common.utils.file.FileUtils;
|
||||
import com.ruoyi.common.utils.file.MimeTypeUtils;
|
||||
import com.ruoyi.framework.web.service.TokenService;
|
||||
import com.ruoyi.system.service.ISysUserService;
|
||||
@ -94,7 +95,7 @@ public class SysProfileController extends BaseController
|
||||
String oldPassword = params.get("oldPassword");
|
||||
String newPassword = params.get("newPassword");
|
||||
LoginUser loginUser = getLoginUser();
|
||||
String userName = loginUser.getUsername();
|
||||
Long userId = loginUser.getUserId();
|
||||
String password = loginUser.getPassword();
|
||||
if (!SecurityUtils.matchesPassword(oldPassword, password))
|
||||
{
|
||||
@ -105,7 +106,7 @@ public class SysProfileController extends BaseController
|
||||
return error("新密码不能与旧密码相同");
|
||||
}
|
||||
newPassword = SecurityUtils.encryptPassword(newPassword);
|
||||
if (userService.resetUserPwd(userName, newPassword) > 0)
|
||||
if (userService.resetUserPwd(userId, newPassword) > 0)
|
||||
{
|
||||
// 更新缓存用户密码&密码最后更新时间
|
||||
loginUser.getUser().setPwdUpdateDate(DateUtils.getNowDate());
|
||||
@ -126,9 +127,14 @@ public class SysProfileController extends BaseController
|
||||
if (!file.isEmpty())
|
||||
{
|
||||
LoginUser loginUser = getLoginUser();
|
||||
String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION);
|
||||
if (userService.updateUserAvatar(loginUser.getUsername(), avatar))
|
||||
String avatar = FileUploadUtils.upload(RuoYiConfig.getAvatarPath(), file, MimeTypeUtils.IMAGE_EXTENSION, true);
|
||||
if (userService.updateUserAvatar(loginUser.getUserId(), avatar))
|
||||
{
|
||||
String oldAvatar = loginUser.getUser().getAvatar();
|
||||
if (StringUtils.isNotEmpty(oldAvatar))
|
||||
{
|
||||
FileUtils.deleteFile(RuoYiConfig.getProfile() + FileUtils.stripPrefix(oldAvatar));
|
||||
}
|
||||
AjaxResult ajax = AjaxResult.success();
|
||||
ajax.put("imgUrl", avatar);
|
||||
// 更新缓存用户头像
|
||||
|
@ -0,0 +1,312 @@
|
||||
package com.ruoyi.web.controller.tool;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.context.request.async.DeferredResult;
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
|
||||
/**
|
||||
* 斑马订单控制器
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Api("斑马订单接口")
|
||||
@RestController
|
||||
@RequestMapping("/tool/banma")
|
||||
@Anonymous
|
||||
public class BanmaOrderController extends BaseController {
|
||||
private static final String AUTH_TOKEN = "Bearer e5V8Vlaf9xh5i31xaI300wbdXEE3iLtgip+JXfzZsb7GShP2XCGhoVzTEVxyc8LH";
|
||||
private static final String API_URL = "https://banma365.cn/api/order/list?recipientName=&page=%d&size=%d&markFlag=0&state=4&_t=%d";
|
||||
private static final String TRACKING_URL = "https://banma365.cn/zebraExpressHub/web/tracking/getByExpressNumber/%s";
|
||||
private static final int CONNECTION_TIMEOUT = 900000000;
|
||||
private static final int READ_TIMEOUT = 900000000;
|
||||
@Autowired
|
||||
private RestTemplate restTemplate;
|
||||
public BanmaOrderController() {
|
||||
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
|
||||
factory.setConnectTimeout(CONNECTION_TIMEOUT);
|
||||
factory.setReadTimeout(READ_TIMEOUT);
|
||||
restTemplate = new RestTemplate(factory);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取斑马订单列表
|
||||
*/
|
||||
@ApiOperation("获取斑马订单列表")
|
||||
@GetMapping("/orders")
|
||||
public R<List<Map<String, Object>>> getOrders(
|
||||
@RequestParam(value = "page", defaultValue = "1") Integer page,
|
||||
@RequestParam(value = "size", defaultValue = "10") Integer size) {
|
||||
try {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Authorization", AUTH_TOKEN);
|
||||
HttpEntity<String> entity = new HttpEntity<>(headers);
|
||||
|
||||
String url = String.format(API_URL, page, size, System.currentTimeMillis());
|
||||
|
||||
ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.GET, entity, Map.class);
|
||||
|
||||
Map<String, Object> responseBody = response.getBody();
|
||||
if (responseBody == null) {
|
||||
return R.ok(Collections.emptyList());
|
||||
}
|
||||
|
||||
Map<String, Object> dataMap = (Map<String, Object>) responseBody.get("data");
|
||||
if (dataMap == null) {
|
||||
return R.ok(Collections.emptyList());
|
||||
}
|
||||
|
||||
List<Map<String, Object>> orders = (List<Map<String, Object>>) dataMap.get("list");
|
||||
|
||||
if (orders == null) {
|
||||
return R.ok(Collections.emptyList());
|
||||
}
|
||||
|
||||
List<Map<String, Object>> resultList = new ArrayList<>(orders.size());
|
||||
for (Map<String, Object> order : orders) {
|
||||
if (order == null) continue;
|
||||
|
||||
Map<String, Object> simplifiedOrder = new HashMap<>();
|
||||
|
||||
// 国际运单号和国际运费
|
||||
String trackingNumber = (String) order.get("internationalTrackingNumber");
|
||||
simplifiedOrder.put("internationalTrackingNumber", trackingNumber);
|
||||
simplifiedOrder.put("internationalShippingFee", order.get("internationalShippingFee"));
|
||||
|
||||
// 获取物流轨迹信息
|
||||
if (trackingNumber != null && !trackingNumber.isEmpty()) {
|
||||
simplifiedOrder.put("trackInfo", getTrackingInfo(trackingNumber));
|
||||
}
|
||||
|
||||
// 处理子订单信息
|
||||
List<Map<String, Object>> subOrders = (List<Map<String, Object>>) order.get("subOrders");
|
||||
if (subOrders != null && !subOrders.isEmpty()) {
|
||||
Map<String, Object> firstSubOrder = subOrders.get(0);
|
||||
if (firstSubOrder != null) {
|
||||
simplifiedOrder.put("poTrackingNumber", firstSubOrder.get("poTrackingNumber"));
|
||||
simplifiedOrder.put("orderedAt", firstSubOrder.get("orderedAt"));
|
||||
simplifiedOrder.put("timeSinceOrder", firstSubOrder.get("timeSinceOrder"));
|
||||
}
|
||||
}
|
||||
|
||||
resultList.add(simplifiedOrder);
|
||||
}
|
||||
|
||||
return R.ok(resultList);
|
||||
} catch (Exception e) {
|
||||
logger.error("获取斑马订单失败", e);
|
||||
return R.fail("获取订单失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取物流轨迹信息
|
||||
*/
|
||||
private String getTrackingInfo(String trackingNumber) {
|
||||
try {
|
||||
if (trackingNumber == null || trackingNumber.isEmpty()) return null;
|
||||
|
||||
String url = String.format(TRACKING_URL, trackingNumber);
|
||||
ResponseEntity<Map> response = restTemplate.getForEntity(url, Map.class);
|
||||
Map<String, Object> responseBody = response.getBody();
|
||||
|
||||
if (responseBody == null) return null;
|
||||
|
||||
if (Integer.valueOf(0).equals(responseBody.get("code"))) {
|
||||
Object dataObj = responseBody.get("data");
|
||||
if (dataObj == null) return null;
|
||||
|
||||
List<Map<String, Object>> trackingData = (List<Map<String, Object>>) dataObj;
|
||||
if (trackingData != null && !trackingData.isEmpty()) {
|
||||
Map<String, Object> firstTrack = trackingData.get(0);
|
||||
if (firstTrack != null) {
|
||||
return (String) firstTrack.get("track");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("获取物流信息失败: {}", trackingNumber);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取物流轨迹信息
|
||||
*/
|
||||
@ApiOperation("获取物流轨迹信息")
|
||||
@GetMapping("/tracking/{trackingNumber}")
|
||||
public R<String> getTracking(@PathVariable("trackingNumber") String trackingNumber) {
|
||||
try {
|
||||
String trackInfo = getTrackingInfo(trackingNumber);
|
||||
return trackInfo != null ? R.ok(trackInfo) : R.fail("未找到物流信息");
|
||||
} catch (Exception e) {
|
||||
return R.fail("获取物流信息失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有页的斑马订单
|
||||
*/
|
||||
@ApiOperation("获取所有页的斑马订单")
|
||||
@GetMapping("/orders/all")
|
||||
public DeferredResult<R<Map<String, Object>>> getAllOrders() {
|
||||
DeferredResult<R<Map<String, Object>>> deferredResult = new DeferredResult<>(1200000L); // 20分钟超时
|
||||
|
||||
// 异步处理请求
|
||||
Thread thread = new Thread(() -> {
|
||||
try {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.set("Authorization", AUTH_TOKEN);
|
||||
HttpEntity<String> entity = new HttpEntity<>(headers);
|
||||
|
||||
int page = 1;
|
||||
int size = 20;
|
||||
|
||||
if (deferredResult.isSetOrExpired()) return;
|
||||
|
||||
String url = String.format(API_URL, page, size, System.currentTimeMillis());
|
||||
ResponseEntity<Map> response = restTemplate.exchange(url, HttpMethod.GET, entity, Map.class);
|
||||
|
||||
Map<String, Object> responseBody = response.getBody();
|
||||
if (responseBody == null || !responseBody.containsKey("data")) {
|
||||
if (!deferredResult.isSetOrExpired()) {
|
||||
deferredResult.setResult(R.fail("API返回数据无效"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> dataMap = (Map<String, Object>) responseBody.get("data");
|
||||
|
||||
// 获取总记录数
|
||||
int totalCount = dataMap.containsKey("total") ? ((Number) dataMap.get("total")).intValue() : 0;
|
||||
List<Map<String, Object>> orders = (List<Map<String, Object>>) dataMap.get("list");
|
||||
|
||||
if (orders == null) {
|
||||
orders = Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Map<String, Object>> allOrders = new ArrayList<>(orders.size());
|
||||
|
||||
for (Map<String, Object> order : orders) {
|
||||
if (order == null) continue;
|
||||
|
||||
Map<String, Object> simplifiedOrder = new HashMap<>();
|
||||
|
||||
// 国际运单号和国际运费
|
||||
String trackingNumber = (String) order.get("internationalTrackingNumber");
|
||||
simplifiedOrder.put("internationalTrackingNumber", trackingNumber);
|
||||
simplifiedOrder.put("internationalShippingFee", order.get("internationalShippingFee"));
|
||||
|
||||
// 获取物流轨迹信息
|
||||
if (trackingNumber != null && !trackingNumber.isEmpty()) {
|
||||
simplifiedOrder.put("trackInfo", getTrackingInfo(trackingNumber));
|
||||
}
|
||||
|
||||
// 处理子订单信息
|
||||
List<Map<String, Object>> subOrders = (List<Map<String, Object>>) order.get("subOrders");
|
||||
if (subOrders != null && !subOrders.isEmpty()) {
|
||||
Map<String, Object> firstSubOrder = subOrders.get(0);
|
||||
if (firstSubOrder != null) {
|
||||
simplifiedOrder.put("poTrackingNumber", firstSubOrder.get("poTrackingNumber"));
|
||||
simplifiedOrder.put("orderedAt", firstSubOrder.get("orderedAt"));
|
||||
simplifiedOrder.put("timeSinceOrder", firstSubOrder.get("timeSinceOrder"));
|
||||
}
|
||||
}
|
||||
|
||||
allOrders.add(simplifiedOrder);
|
||||
}
|
||||
|
||||
// 计算预计总页数
|
||||
int totalPages = (int) Math.ceil((double) totalCount / size);
|
||||
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
resultMap.put("orders", allOrders);
|
||||
resultMap.put("total", totalCount);
|
||||
resultMap.put("totalPages", totalPages);
|
||||
resultMap.put("hasMore", orders.size() == size);
|
||||
resultMap.put("nextPage", 2);
|
||||
|
||||
// 设置结果前再次检查状态
|
||||
if (!deferredResult.isSetOrExpired()) {
|
||||
deferredResult.setResult(R.ok(resultMap));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
deferredResult.setResult(R.fail("获取所有订单失败: " + e.getMessage()));
|
||||
}
|
||||
});
|
||||
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
|
||||
return deferredResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取下一页斑马订单
|
||||
*/
|
||||
@ApiOperation("获取下一页斑马订单")
|
||||
@GetMapping("/orders/next")
|
||||
public DeferredResult<R<Map<String, Object>>> getNextPageOrders(@RequestParam(value = "page", defaultValue = "1") Integer page) {
|
||||
DeferredResult<R<Map<String, Object>>> deferredResult = new DeferredResult<>(120000L); // 2分钟超时
|
||||
|
||||
Thread thread = new Thread(() -> {
|
||||
try {
|
||||
int size = 20;
|
||||
// 若已超时或已完成则不处理
|
||||
if (deferredResult.isSetOrExpired()) return;
|
||||
|
||||
R<List<Map<String, Object>>> pageResult = getOrders(page, size);
|
||||
if (pageResult.getCode() != 200) {
|
||||
if (!deferredResult.isSetOrExpired()) {
|
||||
deferredResult.setResult(R.fail(pageResult.getMsg()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
List<Map<String, Object>> pageData = pageResult.getData();
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
resultMap.put("orders", pageData);
|
||||
resultMap.put("hasMore", pageData != null && pageData.size() == size);
|
||||
resultMap.put("nextPage", page + 1);
|
||||
|
||||
// 设置结果前再次检查状态
|
||||
if (!deferredResult.isSetOrExpired()) {
|
||||
deferredResult.setResult(R.ok(resultMap));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("获取下一页订单失败", e);
|
||||
if (!deferredResult.isSetOrExpired()) {
|
||||
deferredResult.setResult(R.fail("获取订单失败: " + e.getMessage()));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
thread.setDaemon(true);
|
||||
thread.start();
|
||||
|
||||
return deferredResult;
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
package com.ruoyi.web.controller.tool;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
@ -0,0 +1,407 @@
|
||||
package com.ruoyi.web.controller.tool;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.context.request.async.DeferredResult;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.util.ResourceUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.R;
|
||||
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
|
||||
import us.codecraft.webmagic.Page;
|
||||
import us.codecraft.webmagic.Site;
|
||||
import us.codecraft.webmagic.Spider;
|
||||
import us.codecraft.webmagic.processor.PageProcessor;
|
||||
import us.codecraft.webmagic.selector.Html;
|
||||
import us.codecraft.webmagic.downloader.HttpClientDownloader;
|
||||
import us.codecraft.webmagic.proxy.Proxy;
|
||||
import us.codecraft.webmagic.proxy.SimpleProxyProvider;
|
||||
import us.codecraft.webmagic.scheduler.QueueScheduler;
|
||||
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import java.io.InputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.File;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 亚马逊爬虫控制器 - 爬取价格和卖家信息
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Api("亚马逊爬虫功能")
|
||||
@RestController
|
||||
@RequestMapping("/tool/webmagic")
|
||||
@Anonymous
|
||||
public class WebMagicController extends BaseController implements PageProcessor {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(WebMagicController.class);
|
||||
|
||||
// 随机数生成器
|
||||
private final Random random = new Random();
|
||||
|
||||
// 站点配置
|
||||
private final Site site = Site.me()
|
||||
.setRetryTimes(3)
|
||||
.setSleepTime(1000 + random.nextInt(2000))
|
||||
.setTimeOut(15000)
|
||||
.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
|
||||
.addCookie("session-id", "358-1261309-0483141")
|
||||
.addCookie("session-id-time", "2082787201l")
|
||||
.addCookie("i18n-prefs", "JPY")
|
||||
.addCookie("lc-acbjp", "zh_CN")
|
||||
.addCookie("ubid-acbjp", "357-8224002-9668932");
|
||||
|
||||
// 默认代理配置
|
||||
private static final String DEFAULT_PROXY_HOST = "127.0.0.1";
|
||||
private static final String DEFAULT_PROXY_PORT = "7890";
|
||||
|
||||
private String currentProxyName = "";
|
||||
private String currentProxyHost = DEFAULT_PROXY_HOST;
|
||||
private int currentProxyPort = Integer.parseInt(DEFAULT_PROXY_PORT);
|
||||
|
||||
// 存储所有代理节点信息
|
||||
private List<Map<String, Object>> proxyNodes = new ArrayList<>();
|
||||
|
||||
// 爬取结果
|
||||
private Map<String, Object> resultMap = new HashMap<>();
|
||||
|
||||
/**
|
||||
* 初始化加载代理配置
|
||||
*/
|
||||
public WebMagicController() {
|
||||
loadProxyConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* 加载代理配置文件
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void loadProxyConfig() {
|
||||
try {
|
||||
File configFile = ResourceUtils.getFile("classpath:test_proxy.yml");
|
||||
if (configFile.exists()) {
|
||||
Yaml yaml = new Yaml();
|
||||
try (InputStream inputStream = new FileInputStream(configFile)) {
|
||||
Map<String, Object> config = yaml.load(inputStream);
|
||||
if (config != null && config.containsKey("proxies")) {
|
||||
List<Map<String, Object>> proxies = (List<Map<String, Object>>) config.get("proxies");
|
||||
proxyNodes = proxies.stream()
|
||||
.filter(proxy -> proxy != null && proxy.containsKey("name") && proxy.containsKey("server") && proxy.containsKey("port"))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("加载代理配置失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有可用代理节点
|
||||
*/
|
||||
@ApiOperation("获取所有可用代理节点")
|
||||
@GetMapping("/proxies")
|
||||
public R<List<Map<String, String>>> getProxies() {
|
||||
List<Map<String, String>> result = proxyNodes.stream()
|
||||
.map(node -> {
|
||||
Map<String, String> proxyInfo = new HashMap<>();
|
||||
proxyInfo.put("name", (String) node.get("name"));
|
||||
proxyInfo.put("server", (String) node.get("server"));
|
||||
proxyInfo.put("port", String.valueOf(node.get("port")));
|
||||
proxyInfo.put("type", (String) node.get("type"));
|
||||
return proxyInfo;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
return R.ok(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置当前使用的代理节点
|
||||
*/
|
||||
@ApiOperation("设置当前使用的代理节点")
|
||||
@PostMapping("/proxy/set")
|
||||
public R<Map<String, String>> setCurrentProxy(@RequestParam String proxyName) {
|
||||
// 查找匹配的代理节点
|
||||
Map<String, Object> targetProxy = proxyNodes.stream()
|
||||
.filter(node -> proxyName.equals(node.get("name")))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
Map<String, String> result = new HashMap<>();
|
||||
|
||||
if (targetProxy != null) {
|
||||
// 更新当前代理信息
|
||||
currentProxyName = (String) targetProxy.get("name");
|
||||
currentProxyHost = (String) targetProxy.get("server");
|
||||
currentProxyPort = ((Number) targetProxy.get("port")).intValue();
|
||||
|
||||
result.put("name", currentProxyName);
|
||||
result.put("host", currentProxyHost);
|
||||
result.put("port", String.valueOf(currentProxyPort));
|
||||
|
||||
return R.ok(result);
|
||||
} else {
|
||||
return R.fail("未找到指定的代理节点: " + proxyName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(Page page) {
|
||||
try {
|
||||
Html html = page.getHtml();
|
||||
String priceSymbol = html.xpath("//span[@class='a-price-symbol']/text()").toString();
|
||||
String priceWhole = html.xpath("//span[@class='a-price-whole']/text()").toString();
|
||||
|
||||
if (priceSymbol != null && !priceSymbol.isEmpty() && priceWhole != null && !priceWhole.isEmpty()) {
|
||||
resultMap.put("price", priceSymbol + priceWhole);
|
||||
}
|
||||
|
||||
resultMap.put("seller", html.xpath("//a[@id='sellerProfileTriggerId']/text()").toString());
|
||||
resultMap.put("asin", html.xpath("//input[@id='ASIN']/@value").toString());
|
||||
|
||||
String price = (String) resultMap.get("price");
|
||||
String seller = (String) resultMap.get("seller");
|
||||
Object retriesObj = page.getRequest().getExtra("retries");
|
||||
int retries = retriesObj == null ? 0 : (int) retriesObj;
|
||||
if ((price == null || price.isEmpty() || seller == null || seller.isEmpty()) && retries < 1) {
|
||||
String url = page.getUrl().toString();
|
||||
us.codecraft.webmagic.Request request = new us.codecraft.webmagic.Request(url);
|
||||
request.putExtra("retries", retries + 1);
|
||||
page.addTargetRequest(request);
|
||||
try {
|
||||
Thread.sleep(1000 + random.nextInt(2000));
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("解析页面失败", e);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public Site getSite() {
|
||||
return site;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置了代理的下载器
|
||||
*/
|
||||
private HttpClientDownloader getProxyDownloader() {
|
||||
HttpClientDownloader httpClientDownloader = new HttpClientDownloader();
|
||||
clearSystemProxy();
|
||||
System.setProperty("http.proxyHost", DEFAULT_PROXY_HOST);
|
||||
System.setProperty("http.proxyPort", DEFAULT_PROXY_PORT);
|
||||
System.setProperty("https.proxyHost", DEFAULT_PROXY_HOST);
|
||||
System.setProperty("https.proxyPort", DEFAULT_PROXY_PORT);
|
||||
System.setProperty("http.nonProxyHosts", "localhost|127.0.0.1");
|
||||
httpClientDownloader.setProxyProvider(SimpleProxyProvider.from(
|
||||
new Proxy(DEFAULT_PROXY_HOST, Integer.parseInt(DEFAULT_PROXY_PORT))
|
||||
));
|
||||
|
||||
return httpClientDownloader;
|
||||
}
|
||||
|
||||
private void clearSystemProxy() {
|
||||
System.clearProperty("http.proxyHost");
|
||||
System.clearProperty("http.proxyPort");
|
||||
System.clearProperty("https.proxyHost");
|
||||
System.clearProperty("socksProxyHost");
|
||||
System.clearProperty("socksProxyPort");
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建亚马逊产品URL
|
||||
*/
|
||||
private String buildAmazonUrl(String asin) {
|
||||
return "https://www.amazon.co.jp/dp/" + asin;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量爬取亚马逊产品信息
|
||||
*/
|
||||
@ApiOperation("批量爬取亚马逊产品信息")
|
||||
@PostMapping("/batch")
|
||||
public DeferredResult<ResponseEntity<R<List<Map<String, Object>>>>> batchGetAmazonProductInfo(@RequestBody List<String> asinList) {
|
||||
DeferredResult<ResponseEntity<R<List<Map<String, Object>>>>> deferredResult = new DeferredResult<>(500000L);
|
||||
|
||||
if (asinList == null || asinList.isEmpty()) {
|
||||
deferredResult.setResult(ResponseEntity.ok(R.fail("ASIN列表不能为空")));
|
||||
return deferredResult;
|
||||
}
|
||||
|
||||
CompletableFuture.runAsync(() -> {
|
||||
List<Map<String, Object>> resultList = new ArrayList<>();
|
||||
|
||||
try {
|
||||
for (String asin : asinList) {
|
||||
if (asin == null || asin.trim().isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
String url = buildAmazonUrl(asin.trim());
|
||||
resultMap = new HashMap<>();
|
||||
resultMap.put("asin", asin);
|
||||
Spider spider = null;
|
||||
try {
|
||||
spider = Spider.create(this)
|
||||
.addUrl(url)
|
||||
.setDownloader(getProxyDownloader())
|
||||
.thread(1);
|
||||
spider.run();
|
||||
} finally {
|
||||
clearSystemProxy();
|
||||
}
|
||||
|
||||
resultList.add(new HashMap<>(resultMap));
|
||||
|
||||
}
|
||||
deferredResult.setResult(ResponseEntity.ok(R.ok(resultList)));
|
||||
} catch (Exception e) {
|
||||
clearSystemProxy();
|
||||
logger.error("批量爬取失败", e);
|
||||
deferredResult.setResult(ResponseEntity.ok(R.fail("批量爬取失败")));
|
||||
}
|
||||
});
|
||||
return deferredResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试代理节点延迟
|
||||
*/
|
||||
@ApiOperation("测试代理节点延迟")
|
||||
@PostMapping("/proxy/test")
|
||||
public DeferredResult<ResponseEntity<R<List<Map<String, Object>>>>> testProxyDelay(@RequestBody List<String> proxyNames) {
|
||||
DeferredResult<ResponseEntity<R<List<Map<String, Object>>>>> deferredResult = new DeferredResult<>(600000L);
|
||||
CompletableFuture.runAsync(() -> {
|
||||
List<Map<String, Object>> resultList = new ArrayList<>();
|
||||
|
||||
try {
|
||||
for (int i = 0; i < proxyNames.size(); i += 2) {
|
||||
final int currentIndex = i;
|
||||
List<CompletableFuture<Map<String, Object>>> futures = new ArrayList<>();
|
||||
|
||||
futures.add(CompletableFuture.supplyAsync(() -> testSingleProxy(proxyNames.get(currentIndex))));
|
||||
|
||||
if (currentIndex + 1 < proxyNames.size()) {
|
||||
futures.add(CompletableFuture.supplyAsync(() ->
|
||||
testSingleProxy(proxyNames.get(currentIndex + 1)))
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
|
||||
.get(3, TimeUnit.SECONDS);
|
||||
} catch (Exception e) {
|
||||
logger.debug("等待代理测试完成时发生异常", e);
|
||||
}
|
||||
|
||||
for (CompletableFuture<Map<String, Object>> future : futures) {
|
||||
try {
|
||||
if (future.isDone() && !future.isCompletedExceptionally()) {
|
||||
resultList.add(future.join());
|
||||
} else if (!future.isDone()) {
|
||||
Map<String, Object> timeoutResult = new HashMap<>();
|
||||
timeoutResult.put("name", "unknown");
|
||||
timeoutResult.put("status", "fail");
|
||||
timeoutResult.put("message", "测试超时");
|
||||
timeoutResult.put("delay", -1);
|
||||
resultList.add(timeoutResult);
|
||||
}
|
||||
} catch (Exception ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deferredResult.setResult(ResponseEntity.ok(R.ok(resultList)));
|
||||
} catch (Exception e) {
|
||||
logger.error("测试代理延迟失败", e);
|
||||
deferredResult.setResult(ResponseEntity.ok(R.fail("测试代理延迟失败")));
|
||||
}
|
||||
});
|
||||
|
||||
return deferredResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试单个代理节点延迟
|
||||
*/
|
||||
private Map<String, Object> testSingleProxy(String proxyName) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("name", proxyName);
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
try {
|
||||
Map<String, Object> targetProxy = proxyNodes.stream()
|
||||
.filter(node -> proxyName.equals(node.get("name")))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
if (targetProxy == null) {
|
||||
result.put("status", "fail");
|
||||
result.put("message", "未找到指定的代理节点");
|
||||
result.put("delay", -1);
|
||||
return result;
|
||||
}
|
||||
|
||||
String proxyHost = (String) targetProxy.get("server");
|
||||
int proxyPort = ((Number) targetProxy.get("port")).intValue();
|
||||
|
||||
HttpClientDownloader testDownloader = new HttpClientDownloader();
|
||||
clearSystemProxy();
|
||||
testDownloader.setProxyProvider(SimpleProxyProvider.from(new Proxy(proxyHost, proxyPort)));
|
||||
|
||||
Spider.create(new PageProcessor() {
|
||||
@Override
|
||||
public void process(Page page) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Site getSite() {
|
||||
return Site.me()
|
||||
.setRetryTimes(0)
|
||||
.setSleepTime(100)
|
||||
.setTimeOut(2000); // 减少超时时间为2秒
|
||||
}
|
||||
})
|
||||
.setDownloader(testDownloader)
|
||||
.addUrl("http://www.gstatic.com/generate_204")
|
||||
.thread(1)
|
||||
.run();
|
||||
|
||||
long endTime = System.currentTimeMillis();
|
||||
|
||||
result.put("status", "success");
|
||||
result.put("delay", endTime - startTime);
|
||||
|
||||
} catch (Exception e) {
|
||||
result.put("status", "fail");
|
||||
result.put("message", "连接超时或失败");
|
||||
result.put("delay", -1);
|
||||
} finally {
|
||||
clearSystemProxy();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
@ -6,9 +6,9 @@ spring:
|
||||
druid:
|
||||
# 主库数据源
|
||||
master:
|
||||
url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
url: jdbc:mysql://192.168.1.89:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
username: root
|
||||
password: password
|
||||
password: 123123
|
||||
# 从库数据源
|
||||
slave:
|
||||
# 从数据源开关/默认关闭
|
||||
|
@ -1,129 +1,129 @@
|
||||
# 项目相关配置
|
||||
ruoyi:
|
||||
# 名称
|
||||
name: RuoYi
|
||||
# 版本
|
||||
version: 3.8.9
|
||||
# 版权年份
|
||||
copyrightYear: 2025
|
||||
# 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
|
||||
profile: D:/ruoyi/uploadPath
|
||||
# 获取ip地址开关
|
||||
addressEnabled: false
|
||||
# 验证码类型 math 数字计算 char 字符验证
|
||||
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:
|
||||
level:
|
||||
com.ruoyi: debug
|
||||
org.springframework: warn
|
||||
|
||||
# 用户配置
|
||||
user:
|
||||
password:
|
||||
# 密码最大错误次数
|
||||
maxRetryCount: 5
|
||||
# 密码锁定时间(默认10分钟)
|
||||
lockTime: 10
|
||||
|
||||
# Spring配置
|
||||
spring:
|
||||
# 资源信息
|
||||
messages:
|
||||
# 国际化资源文件路径
|
||||
basename: i18n/messages
|
||||
profiles:
|
||||
active: druid
|
||||
# 文件上传
|
||||
servlet:
|
||||
multipart:
|
||||
# 单个文件大小
|
||||
max-file-size: 10MB
|
||||
# 设置总上传的文件大小
|
||||
max-request-size: 20MB
|
||||
# 服务模块
|
||||
devtools:
|
||||
restart:
|
||||
# 热部署开关
|
||||
enabled: true
|
||||
# redis 配置
|
||||
redis:
|
||||
# 地址
|
||||
host: localhost
|
||||
# 端口,默认为6379
|
||||
port: 6379
|
||||
# 数据库索引
|
||||
database: 0
|
||||
# 密码
|
||||
password:
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池中的最小空闲连接
|
||||
min-idle: 0
|
||||
# 连接池中的最大空闲连接
|
||||
max-idle: 8
|
||||
# 连接池的最大数据库连接数
|
||||
max-active: 8
|
||||
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-wait: -1ms
|
||||
|
||||
# token配置
|
||||
token:
|
||||
# 令牌自定义标识
|
||||
header: Authorization
|
||||
# 令牌密钥
|
||||
secret: abcdefghijklmnopqrstuvwxyz
|
||||
# 令牌有效期(默认30分钟)
|
||||
expireTime: 30
|
||||
|
||||
# MyBatis配置
|
||||
mybatis:
|
||||
# 搜索指定包别名
|
||||
typeAliasesPackage: com.ruoyi.**.domain
|
||||
# 配置mapper的扫描,找到所有的mapper.xml映射文件
|
||||
mapperLocations: classpath*:mapper/**/*Mapper.xml
|
||||
# 加载全局的配置文件
|
||||
configLocation: classpath:mybatis/mybatis-config.xml
|
||||
|
||||
# PageHelper分页插件
|
||||
pagehelper:
|
||||
helperDialect: mysql
|
||||
supportMethodsArguments: true
|
||||
params: count=countSql
|
||||
|
||||
# Swagger配置
|
||||
swagger:
|
||||
# 是否开启swagger
|
||||
enabled: true
|
||||
# 请求前缀
|
||||
pathMapping: /dev-api
|
||||
|
||||
# 防止XSS攻击
|
||||
xss:
|
||||
# 过滤开关
|
||||
enabled: true
|
||||
# 排除链接(多个用逗号分隔)
|
||||
excludes: /system/notice
|
||||
# 匹配链接
|
||||
urlPatterns: /system/*,/monitor/*,/tool/*
|
||||
# 项目相关配置
|
||||
ruoyi:
|
||||
# 名称
|
||||
name: RuoYi
|
||||
# 版本
|
||||
version: 3.9.0
|
||||
# 版权年份
|
||||
copyrightYear: 2025
|
||||
# 文件路径 示例( Windows配置D:/ruoyi/uploadPath,Linux配置 /home/ruoyi/uploadPath)
|
||||
profile: D:/ruoyi/uploadPath
|
||||
# 获取ip地址开关
|
||||
addressEnabled: false
|
||||
# 验证码类型 math 数字计算 char 字符验证
|
||||
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:
|
||||
level:
|
||||
com.ruoyi: debug
|
||||
org.springframework: warn
|
||||
|
||||
# 用户配置
|
||||
user:
|
||||
password:
|
||||
# 密码最大错误次数
|
||||
maxRetryCount: 5
|
||||
# 密码锁定时间(默认10分钟)
|
||||
lockTime: 10
|
||||
|
||||
# Spring配置
|
||||
spring:
|
||||
# 资源信息
|
||||
messages:
|
||||
# 国际化资源文件路径
|
||||
basename: i18n/messages
|
||||
profiles:
|
||||
active: druid
|
||||
# 文件上传
|
||||
servlet:
|
||||
multipart:
|
||||
# 单个文件大小
|
||||
max-file-size: 10MB
|
||||
# 设置总上传的文件大小
|
||||
max-request-size: 20MB
|
||||
# 服务模块
|
||||
devtools:
|
||||
restart:
|
||||
# 热部署开关
|
||||
enabled: true
|
||||
# redis 配置
|
||||
redis:
|
||||
# 地址
|
||||
host: localhost
|
||||
# 端口,默认为6379
|
||||
port: 6379
|
||||
# 数据库索引
|
||||
database: 0
|
||||
# 密码
|
||||
password:
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池中的最小空闲连接
|
||||
min-idle: 0
|
||||
# 连接池中的最大空闲连接
|
||||
max-idle: 8
|
||||
# 连接池的最大数据库连接数
|
||||
max-active: 8
|
||||
# #连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-wait: -1ms
|
||||
|
||||
# token配置
|
||||
token:
|
||||
# 令牌自定义标识
|
||||
header: Authorization
|
||||
# 令牌密钥
|
||||
secret: abcdefghijklmnopqrstuvwxyz
|
||||
# 令牌有效期(默认30分钟)
|
||||
expireTime: 30
|
||||
|
||||
# MyBatis配置
|
||||
mybatis:
|
||||
# 搜索指定包别名
|
||||
typeAliasesPackage: com.ruoyi.**.domain
|
||||
# 配置mapper的扫描,找到所有的mapper.xml映射文件
|
||||
mapperLocations: classpath*:mapper/**/*Mapper.xml
|
||||
# 加载全局的配置文件
|
||||
configLocation: classpath:mybatis/mybatis-config.xml
|
||||
|
||||
# PageHelper分页插件
|
||||
pagehelper:
|
||||
helperDialect: mysql
|
||||
supportMethodsArguments: true
|
||||
params: count=countSql
|
||||
|
||||
# Swagger配置
|
||||
swagger:
|
||||
# 是否开启swagger
|
||||
enabled: true
|
||||
# 请求前缀
|
||||
pathMapping: /dev-api
|
||||
|
||||
# 防止XSS攻击
|
||||
xss:
|
||||
# 过滤开关
|
||||
enabled: true
|
||||
# 排除链接(多个用逗号分隔)
|
||||
excludes: /system/notice,/tool/webmagic
|
||||
# 匹配链接
|
||||
urlPatterns: /system/*,/monitor/*,/tool/*
|
||||
|
BIN
ruoyi-admin/src/main/resources/test_proxy.yml
Normal file
BIN
ruoyi-admin/src/main/resources/test_proxy.yml
Normal file
Binary file not shown.
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>ruoyi</artifactId>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<version>3.8.9</version>
|
||||
<version>3.9.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
@ -13,11 +13,12 @@ import com.ruoyi.common.exception.file.FileSizeLimitExceededException;
|
||||
import com.ruoyi.common.exception.file.InvalidExtensionException;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.uuid.IdUtils;
|
||||
import com.ruoyi.common.utils.uuid.Seq;
|
||||
|
||||
/**
|
||||
* 文件上传工具类
|
||||
*
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
public class FileUploadUtils
|
||||
@ -102,15 +103,35 @@ public class FileUploadUtils
|
||||
throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
|
||||
InvalidExtensionException
|
||||
{
|
||||
int fileNamelength = Objects.requireNonNull(file.getOriginalFilename()).length();
|
||||
if (fileNamelength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
|
||||
return upload(baseDir, file, allowedExtension, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件上传
|
||||
*
|
||||
* @param baseDir 相对应用的基目录
|
||||
* @param file 上传的文件
|
||||
* @param useCustomNaming 系统自定义文件名
|
||||
* @param allowedExtension 上传文件类型
|
||||
* @return 返回上传成功的文件名
|
||||
* @throws FileSizeLimitExceededException 如果超出最大大小
|
||||
* @throws FileNameLengthLimitExceededException 文件名太长
|
||||
* @throws IOException 比如读写文件出错时
|
||||
* @throws InvalidExtensionException 文件校验异常
|
||||
*/
|
||||
public static final String upload(String baseDir, MultipartFile file, String[] allowedExtension, boolean useCustomNaming)
|
||||
throws FileSizeLimitExceededException, IOException, FileNameLengthLimitExceededException,
|
||||
InvalidExtensionException
|
||||
{
|
||||
int fileNameLength = Objects.requireNonNull(file.getOriginalFilename()).length();
|
||||
if (fileNameLength > FileUploadUtils.DEFAULT_FILE_NAME_LENGTH)
|
||||
{
|
||||
throw new FileNameLengthLimitExceededException(FileUploadUtils.DEFAULT_FILE_NAME_LENGTH);
|
||||
}
|
||||
|
||||
assertAllowed(file, allowedExtension);
|
||||
|
||||
String fileName = extractFilename(file);
|
||||
String fileName = useCustomNaming ? uuidFilename(file) : extractFilename(file);
|
||||
|
||||
String absPath = getAbsoluteFile(baseDir, fileName).getAbsolutePath();
|
||||
file.transferTo(Paths.get(absPath));
|
||||
@ -118,12 +139,19 @@ public class FileUploadUtils
|
||||
}
|
||||
|
||||
/**
|
||||
* 编码文件名
|
||||
* 编码文件名(日期格式目录 + 原文件名 + 序列值 + 后缀)
|
||||
*/
|
||||
public static final String extractFilename(MultipartFile file)
|
||||
{
|
||||
return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(),
|
||||
FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));
|
||||
return StringUtils.format("{}/{}_{}.{}", DateUtils.datePath(), FilenameUtils.getBaseName(file.getOriginalFilename()), Seq.getId(Seq.uploadSeqType), getExtension(file));
|
||||
}
|
||||
|
||||
/**
|
||||
* 编编码文件名(日期格式目录 + UUID + 后缀)
|
||||
*/
|
||||
public static final String uuidFilename(MultipartFile file)
|
||||
{
|
||||
return StringUtils.format("{}/{}.{}", DateUtils.datePath(), IdUtils.fastSimpleUUID(), getExtension(file));
|
||||
}
|
||||
|
||||
public static final File getAbsoluteFile(String uploadDir, String fileName) throws IOException
|
||||
@ -216,7 +244,7 @@ public class FileUploadUtils
|
||||
|
||||
/**
|
||||
* 获取文件名的后缀
|
||||
*
|
||||
*
|
||||
* @param file 表单文件
|
||||
* @return 后缀名
|
||||
*/
|
||||
|
@ -11,13 +11,14 @@ import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
import com.ruoyi.common.config.RuoYiConfig;
|
||||
import com.ruoyi.common.constant.Constants;
|
||||
import com.ruoyi.common.utils.DateUtils;
|
||||
import com.ruoyi.common.utils.StringUtils;
|
||||
import com.ruoyi.common.utils.uuid.IdUtils;
|
||||
import org.apache.commons.io.FilenameUtils;
|
||||
|
||||
/**
|
||||
* 文件处理工具类
|
||||
@ -103,6 +104,17 @@ public class FileUtils
|
||||
return FileUploadUtils.getPathFileName(uploadDir, pathName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除路径中的请求前缀片段
|
||||
*
|
||||
* @param filePath 文件路径
|
||||
* @return 移除后的文件路径
|
||||
*/
|
||||
public static String stripPrefix(String filePath)
|
||||
{
|
||||
return StringUtils.substringAfter(filePath, Constants.RESOURCE_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
*
|
||||
|
@ -186,11 +186,6 @@ public class ExcelUtil<T>
|
||||
*/
|
||||
private Map<Integer, Double> statistics = new HashMap<Integer, Double>();
|
||||
|
||||
/**
|
||||
* 数字格式
|
||||
*/
|
||||
private static final DecimalFormat DOUBLE_FORMAT = new DecimalFormat("######0.00");
|
||||
|
||||
/**
|
||||
* 实体对象
|
||||
*/
|
||||
@ -861,6 +856,7 @@ public class ExcelUtil<T>
|
||||
style = wb.createCellStyle();
|
||||
style.setAlignment(HorizontalAlignment.CENTER);
|
||||
style.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||
style.setDataFormat(dataFormat.getFormat("######0.00"));
|
||||
Font totalFont = wb.createFont();
|
||||
totalFont.setFontName("Arial");
|
||||
totalFont.setFontHeightInPoints((short) 10);
|
||||
@ -1442,7 +1438,7 @@ public class ExcelUtil<T>
|
||||
{
|
||||
cell = row.createCell(key);
|
||||
cell.setCellStyle(styles.get("total"));
|
||||
cell.setCellValue(DOUBLE_FORMAT.format(statistics.get(key)));
|
||||
cell.setCellValue(statistics.get(key));
|
||||
}
|
||||
statistics.clear();
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>ruoyi</artifactId>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<version>3.8.9</version>
|
||||
<version>3.9.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>ruoyi</artifactId>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<version>3.8.9</version>
|
||||
<version>3.9.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>ruoyi</artifactId>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<version>3.8.9</version>
|
||||
<version>3.9.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
@ -131,11 +131,11 @@ public class ScheduleUtils
|
||||
int count = StringUtils.countMatches(packageName, ".");
|
||||
if (count > 1)
|
||||
{
|
||||
return StringUtils.containsAnyIgnoreCase(invokeTarget, Constants.JOB_WHITELIST_STR);
|
||||
return StringUtils.startsWithAny(invokeTarget, Constants.JOB_WHITELIST_STR);
|
||||
}
|
||||
Object obj = SpringUtils.getBean(StringUtils.split(invokeTarget, ".")[0]);
|
||||
String beanPackageName = obj.getClass().getPackage().getName();
|
||||
return StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_WHITELIST_STR)
|
||||
&& !StringUtils.containsAnyIgnoreCase(beanPackageName, Constants.JOB_ERROR_STR);
|
||||
return StringUtils.startsWithAny(beanPackageName, Constants.JOB_WHITELIST_STR)
|
||||
&& !StringUtils.startsWithAny(beanPackageName, Constants.JOB_ERROR_STR);
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<artifactId>ruoyi</artifactId>
|
||||
<groupId>com.ruoyi</groupId>
|
||||
<version>3.8.9</version>
|
||||
<version>3.9.0</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
|
@ -70,20 +70,20 @@ public interface SysUserMapper
|
||||
/**
|
||||
* 修改用户头像
|
||||
*
|
||||
* @param userName 用户名
|
||||
* @param userId 用户ID
|
||||
* @param avatar 头像地址
|
||||
* @return 结果
|
||||
*/
|
||||
public int updateUserAvatar(@Param("userName") String userName, @Param("avatar") String avatar);
|
||||
public int updateUserAvatar(@Param("userId") Long userId, @Param("avatar") String avatar);
|
||||
|
||||
/**
|
||||
* 重置用户密码
|
||||
*
|
||||
* @param userName 用户名
|
||||
* @param userId 用户ID
|
||||
* @param password 密码
|
||||
* @return 结果
|
||||
*/
|
||||
public int resetUserPwd(@Param("userName") String userName, @Param("password") String password);
|
||||
public int resetUserPwd(@Param("userId") Long userId, @Param("password") String password);
|
||||
|
||||
/**
|
||||
* 通过用户ID删除用户
|
||||
|
@ -155,11 +155,11 @@ public interface ISysUserService
|
||||
/**
|
||||
* 修改用户头像
|
||||
*
|
||||
* @param userName 用户名
|
||||
* @param userId 用户ID
|
||||
* @param avatar 头像地址
|
||||
* @return 结果
|
||||
*/
|
||||
public boolean updateUserAvatar(String userName, String avatar);
|
||||
public boolean updateUserAvatar(Long userId, String avatar);
|
||||
|
||||
/**
|
||||
* 重置用户密码
|
||||
@ -172,11 +172,11 @@ public interface ISysUserService
|
||||
/**
|
||||
* 重置用户密码
|
||||
*
|
||||
* @param userName 用户名
|
||||
* @param userId 用户ID
|
||||
* @param password 密码
|
||||
* @return 结果
|
||||
*/
|
||||
public int resetUserPwd(String userName, String password);
|
||||
public int resetUserPwd(Long userId, String password);
|
||||
|
||||
/**
|
||||
* 通过用户ID删除用户
|
||||
|
@ -344,14 +344,14 @@ public class SysUserServiceImpl implements ISysUserService
|
||||
/**
|
||||
* 修改用户头像
|
||||
*
|
||||
* @param userName 用户名
|
||||
* @param userId 用户ID
|
||||
* @param avatar 头像地址
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public boolean updateUserAvatar(String userName, String avatar)
|
||||
public boolean updateUserAvatar(Long userId, String avatar)
|
||||
{
|
||||
return userMapper.updateUserAvatar(userName, avatar) > 0;
|
||||
return userMapper.updateUserAvatar(userId, avatar) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -369,14 +369,14 @@ public class SysUserServiceImpl implements ISysUserService
|
||||
/**
|
||||
* 重置用户密码
|
||||
*
|
||||
* @param userName 用户名
|
||||
* @param userId 用户ID
|
||||
* @param password 密码
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
public int resetUserPwd(String userName, String password)
|
||||
public int resetUserPwd(Long userId, String password)
|
||||
{
|
||||
return userMapper.resetUserPwd(userName, password);
|
||||
return userMapper.resetUserPwd(userId, password);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,13 +19,13 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="loginIp" column="login_ip" />
|
||||
<result property="loginDate" column="login_date" />
|
||||
<result property="pwdUpdateDate" column="pwd_update_date" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="remark" column="remark" />
|
||||
<association property="dept" javaType="SysDept" resultMap="deptResult" />
|
||||
<collection property="roles" javaType="java.util.List" resultMap="RoleResult" />
|
||||
<result property="createBy" column="create_by" />
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="updateBy" column="update_by" />
|
||||
<result property="updateTime" column="update_time" />
|
||||
<result property="remark" column="remark" />
|
||||
<association property="dept" javaType="SysDept" resultMap="deptResult" />
|
||||
<collection property="roles" javaType="java.util.List" resultMap="RoleResult" />
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="deptResult" type="SysDept">
|
||||
@ -202,11 +202,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</update>
|
||||
|
||||
<update id="updateUserAvatar" parameterType="SysUser">
|
||||
update sys_user set avatar = #{avatar} where user_name = #{userName}
|
||||
update sys_user set avatar = #{avatar} where user_id = #{userId}
|
||||
</update>
|
||||
|
||||
<update id="resetUserPwd" parameterType="SysUser">
|
||||
update sys_user set pwd_update_date = sysdate(), password = #{password} where user_name = #{userName}
|
||||
update sys_user set pwd_update_date = sysdate(), password = #{password} where user_id = #{userId}
|
||||
</update>
|
||||
|
||||
<delete id="deleteUserById" parameterType="Long">
|
||||
|
@ -1,72 +1,73 @@
|
||||
{
|
||||
"name": "ruoyi",
|
||||
"version": "3.8.9",
|
||||
"description": "若依管理系统",
|
||||
"author": "若依",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "vue-cli-service serve",
|
||||
"build:prod": "vue-cli-service build",
|
||||
"build:stage": "vue-cli-service build --mode staging",
|
||||
"preview": "node build/index.js --preview"
|
||||
},
|
||||
"keywords": [
|
||||
"vue",
|
||||
"admin",
|
||||
"dashboard",
|
||||
"element-ui",
|
||||
"boilerplate",
|
||||
"admin-template",
|
||||
"management-system"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://gitee.com/y_project/RuoYi-Vue.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@riophae/vue-treeselect": "0.4.0",
|
||||
"axios": "0.28.1",
|
||||
"clipboard": "2.0.8",
|
||||
"core-js": "3.37.1",
|
||||
"echarts": "5.4.0",
|
||||
"element-ui": "2.15.14",
|
||||
"file-saver": "2.0.5",
|
||||
"fuse.js": "6.4.3",
|
||||
"highlight.js": "9.18.5",
|
||||
"js-beautify": "1.13.0",
|
||||
"js-cookie": "3.0.1",
|
||||
"jsencrypt": "3.0.0-rc.1",
|
||||
"nprogress": "0.2.0",
|
||||
"quill": "2.0.2",
|
||||
"screenfull": "5.0.2",
|
||||
"sortablejs": "1.10.2",
|
||||
"splitpanes": "2.4.1",
|
||||
"vue": "2.6.12",
|
||||
"vue-count-to": "1.0.13",
|
||||
"vue-cropper": "0.5.5",
|
||||
"vue-router": "3.4.9",
|
||||
"vuedraggable": "2.24.3",
|
||||
"vuex": "3.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "4.4.6",
|
||||
"@vue/cli-service": "4.4.6",
|
||||
"babel-plugin-dynamic-import-node": "2.3.3",
|
||||
"chalk": "4.1.0",
|
||||
"compression-webpack-plugin": "6.1.2",
|
||||
"connect": "3.6.6",
|
||||
"sass": "1.32.13",
|
||||
"sass-loader": "10.1.1",
|
||||
"script-ext-html-webpack-plugin": "2.1.5",
|
||||
"svg-sprite-loader": "5.1.1",
|
||||
"vue-template-compiler": "2.6.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.9",
|
||||
"npm": ">= 3.0.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions"
|
||||
]
|
||||
}
|
||||
{
|
||||
"name": "ruoyi",
|
||||
"version": "3.9.0",
|
||||
"description": "若依管理系统",
|
||||
"author": "若依",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "vue-cli-service serve",
|
||||
"build:prod": "vue-cli-service build",
|
||||
"build:stage": "vue-cli-service build --mode staging",
|
||||
"preview": "node build/index.js --preview"
|
||||
},
|
||||
"keywords": [
|
||||
"vue",
|
||||
"admin",
|
||||
"dashboard",
|
||||
"element-ui",
|
||||
"boilerplate",
|
||||
"admin-template",
|
||||
"management-system"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://gitee.com/y_project/RuoYi-Vue.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@riophae/vue-treeselect": "0.4.0",
|
||||
"axios": "0.28.1",
|
||||
"clipboard": "2.0.8",
|
||||
"core-js": "^3.43.0",
|
||||
"echarts": "5.4.0",
|
||||
"element-ui": "2.15.14",
|
||||
"file-saver": "2.0.5",
|
||||
"fuse.js": "6.4.3",
|
||||
"highlight.js": "9.18.5",
|
||||
"js-beautify": "1.13.0",
|
||||
"js-cookie": "3.0.1",
|
||||
"jsencrypt": "3.0.0-rc.1",
|
||||
"nprogress": "0.2.0",
|
||||
"quill": "2.0.2",
|
||||
"screenfull": "5.0.2",
|
||||
"sortablejs": "1.10.2",
|
||||
"splitpanes": "2.4.1",
|
||||
"vue": "2.6.12",
|
||||
"vue-count-to": "1.0.13",
|
||||
"vue-cropper": "0.5.5",
|
||||
"vue-router": "3.4.9",
|
||||
"vuedraggable": "2.24.3",
|
||||
"vuex": "3.6.0",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "4.4.6",
|
||||
"@vue/cli-service": "4.4.6",
|
||||
"babel-plugin-dynamic-import-node": "2.3.3",
|
||||
"chalk": "4.1.0",
|
||||
"compression-webpack-plugin": "6.1.2",
|
||||
"connect": "3.6.6",
|
||||
"sass": "1.32.13",
|
||||
"sass-loader": "10.1.1",
|
||||
"script-ext-html-webpack-plugin": "2.1.5",
|
||||
"svg-sprite-loader": "5.1.1",
|
||||
"vue-template-compiler": "2.6.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.9",
|
||||
"npm": ">= 3.0.0"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions"
|
||||
]
|
||||
}
|
||||
|
@ -1,208 +1,208 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="renderer" content="webkit">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= webpackConfig.name %></title>
|
||||
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
.chromeframe {
|
||||
margin: 0.2em 0;
|
||||
background: #ccc;
|
||||
color: #000;
|
||||
padding: 0.2em 0;
|
||||
}
|
||||
|
||||
#loader-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 999999;
|
||||
}
|
||||
|
||||
#loader {
|
||||
display: block;
|
||||
position: relative;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
margin: -75px 0 0 -75px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid transparent;
|
||||
border-top-color: #FFF;
|
||||
-webkit-animation: spin 2s linear infinite;
|
||||
-ms-animation: spin 2s linear infinite;
|
||||
-moz-animation: spin 2s linear infinite;
|
||||
-o-animation: spin 2s linear infinite;
|
||||
animation: spin 2s linear infinite;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
#loader:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
bottom: 5px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid transparent;
|
||||
border-top-color: #FFF;
|
||||
-webkit-animation: spin 3s linear infinite;
|
||||
-moz-animation: spin 3s linear infinite;
|
||||
-o-animation: spin 3s linear infinite;
|
||||
-ms-animation: spin 3s linear infinite;
|
||||
animation: spin 3s linear infinite;
|
||||
}
|
||||
|
||||
#loader:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: 15px;
|
||||
right: 15px;
|
||||
bottom: 15px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid transparent;
|
||||
border-top-color: #FFF;
|
||||
-moz-animation: spin 1.5s linear infinite;
|
||||
-o-animation: spin 1.5s linear infinite;
|
||||
-ms-animation: spin 1.5s linear infinite;
|
||||
-webkit-animation: spin 1.5s linear infinite;
|
||||
animation: spin 1.5s linear infinite;
|
||||
}
|
||||
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
-ms-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
-ms-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
-ms-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
-ms-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#loader-wrapper .loader-section {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 51%;
|
||||
height: 100%;
|
||||
background: #7171C6;
|
||||
z-index: 1000;
|
||||
-webkit-transform: translateX(0);
|
||||
-ms-transform: translateX(0);
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
#loader-wrapper .loader-section.section-left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#loader-wrapper .loader-section.section-right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
|
||||
.loaded #loader-wrapper .loader-section.section-left {
|
||||
-webkit-transform: translateX(-100%);
|
||||
-ms-transform: translateX(-100%);
|
||||
transform: translateX(-100%);
|
||||
-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);
|
||||
}
|
||||
|
||||
.loaded #loader-wrapper .loader-section.section-right {
|
||||
-webkit-transform: translateX(100%);
|
||||
-ms-transform: translateX(100%);
|
||||
transform: translateX(100%);
|
||||
-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);
|
||||
}
|
||||
|
||||
.loaded #loader {
|
||||
opacity: 0;
|
||||
-webkit-transition: all 0.3s ease-out;
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
|
||||
.loaded #loader-wrapper {
|
||||
visibility: hidden;
|
||||
-webkit-transform: translateY(-100%);
|
||||
-ms-transform: translateY(-100%);
|
||||
transform: translateY(-100%);
|
||||
-webkit-transition: all 0.3s 1s ease-out;
|
||||
transition: all 0.3s 1s ease-out;
|
||||
}
|
||||
|
||||
.no-js #loader-wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.no-js h1 {
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
#loader-wrapper .load_title {
|
||||
font-family: 'Open Sans';
|
||||
color: #FFF;
|
||||
font-size: 19px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
z-index: 9999999999999;
|
||||
position: absolute;
|
||||
top: 60%;
|
||||
opacity: 1;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
#loader-wrapper .load_title span {
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
font-size: 13px;
|
||||
color: #FFF;
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div id="loader-wrapper">
|
||||
<div id="loader"></div>
|
||||
<div class="loader-section section-left"></div>
|
||||
<div class="loader-section section-right"></div>
|
||||
<div class="load_title">正在加载系统资源,请耐心等待</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<meta name="renderer" content="webkit">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
|
||||
<title><%= webpackConfig.name %></title>
|
||||
<!--[if lt IE 11]><script>window.location.href='/html/ie.html';</script><![endif]-->
|
||||
<style>
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
.chromeframe {
|
||||
margin: 0.2em 0;
|
||||
background: #ccc;
|
||||
color: #000;
|
||||
padding: 0.2em 0;
|
||||
}
|
||||
|
||||
#loader-wrapper {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 999999;
|
||||
}
|
||||
|
||||
#loader {
|
||||
display: block;
|
||||
position: relative;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
margin: -75px 0 0 -75px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid transparent;
|
||||
border-top-color: #FFF;
|
||||
-webkit-animation: spin 2s linear infinite;
|
||||
-ms-animation: spin 2s linear infinite;
|
||||
-moz-animation: spin 2s linear infinite;
|
||||
-o-animation: spin 2s linear infinite;
|
||||
animation: spin 2s linear infinite;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
#loader:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
right: 5px;
|
||||
bottom: 5px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid transparent;
|
||||
border-top-color: #FFF;
|
||||
-webkit-animation: spin 3s linear infinite;
|
||||
-moz-animation: spin 3s linear infinite;
|
||||
-o-animation: spin 3s linear infinite;
|
||||
-ms-animation: spin 3s linear infinite;
|
||||
animation: spin 3s linear infinite;
|
||||
}
|
||||
|
||||
#loader:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
left: 15px;
|
||||
right: 15px;
|
||||
bottom: 15px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid transparent;
|
||||
border-top-color: #FFF;
|
||||
-moz-animation: spin 1.5s linear infinite;
|
||||
-o-animation: spin 1.5s linear infinite;
|
||||
-ms-animation: spin 1.5s linear infinite;
|
||||
-webkit-animation: spin 1.5s linear infinite;
|
||||
animation: spin 1.5s linear infinite;
|
||||
}
|
||||
|
||||
|
||||
@-webkit-keyframes spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
-ms-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
-ms-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
-webkit-transform: rotate(0deg);
|
||||
-ms-transform: rotate(0deg);
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
-webkit-transform: rotate(360deg);
|
||||
-ms-transform: rotate(360deg);
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#loader-wrapper .loader-section {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
width: 51%;
|
||||
height: 100%;
|
||||
background: #7171C6;
|
||||
z-index: 1000;
|
||||
-webkit-transform: translateX(0);
|
||||
-ms-transform: translateX(0);
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
#loader-wrapper .loader-section.section-left {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
#loader-wrapper .loader-section.section-right {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
|
||||
.loaded #loader-wrapper .loader-section.section-left {
|
||||
-webkit-transform: translateX(-100%);
|
||||
-ms-transform: translateX(-100%);
|
||||
transform: translateX(-100%);
|
||||
-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);
|
||||
}
|
||||
|
||||
.loaded #loader-wrapper .loader-section.section-right {
|
||||
-webkit-transform: translateX(100%);
|
||||
-ms-transform: translateX(100%);
|
||||
transform: translateX(100%);
|
||||
-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);
|
||||
}
|
||||
|
||||
.loaded #loader {
|
||||
opacity: 0;
|
||||
-webkit-transition: all 0.3s ease-out;
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
|
||||
.loaded #loader-wrapper {
|
||||
visibility: hidden;
|
||||
-webkit-transform: translateY(-100%);
|
||||
-ms-transform: translateY(-100%);
|
||||
transform: translateY(-100%);
|
||||
-webkit-transition: all 0.3s 1s ease-out;
|
||||
transition: all 0.3s 1s ease-out;
|
||||
}
|
||||
|
||||
.no-js #loader-wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.no-js h1 {
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
#loader-wrapper .load_title {
|
||||
font-family: 'Open Sans';
|
||||
color: #FFF;
|
||||
font-size: 19px;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
z-index: 9999999999999;
|
||||
position: absolute;
|
||||
top: 60%;
|
||||
opacity: 1;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
#loader-wrapper .load_title span {
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
font-size: 13px;
|
||||
color: #FFF;
|
||||
opacity: 0.5;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div id="loader-wrapper">
|
||||
<div id="loader"></div>
|
||||
<div class="loader-section section-left"></div>
|
||||
<div class="loader-section section-right"></div>
|
||||
<div class="load_title">正在加载系统资源,请耐心等待</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
44
ruoyi-ui/src/api/prod/products.js
Normal file
44
ruoyi-ui/src/api/prod/products.js
Normal file
@ -0,0 +1,44 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 查询prod列表
|
||||
export function listProducts(query) {
|
||||
return request({
|
||||
url: '/prod/products/list',
|
||||
method: 'get',
|
||||
params: query
|
||||
})
|
||||
}
|
||||
|
||||
// 查询prod详细
|
||||
export function getProducts(productId) {
|
||||
return request({
|
||||
url: '/prod/products/' + productId,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增prod
|
||||
export function addProducts(data) {
|
||||
return request({
|
||||
url: '/prod/products',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 修改prod
|
||||
export function updateProducts(data) {
|
||||
return request({
|
||||
url: '/prod/products',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 删除prod
|
||||
export function delProducts(productId) {
|
||||
return request({
|
||||
url: '/prod/products/' + productId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
@ -87,6 +87,22 @@ export const constantRoutes = [
|
||||
meta: { title: '个人中心', icon: 'user' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: '/banma',
|
||||
component: Layout,
|
||||
hidden: false,
|
||||
redirect: 'noredirect',
|
||||
name: 'Banma',
|
||||
meta: { title: '斑马订单', icon: 'shopping' },
|
||||
children: [
|
||||
{
|
||||
path: 'orders',
|
||||
component: () => import('@/views/banma/orders/index'),
|
||||
name: 'BanmaOrders',
|
||||
meta: { title: '订单管理', icon: 'list' }
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
|
100
ruoyi-ui/src/utils/excel.js
Normal file
100
ruoyi-ui/src/utils/excel.js
Normal file
@ -0,0 +1,100 @@
|
||||
/**
|
||||
* Excel工具类 - 用于导入导出Excel文件
|
||||
*/
|
||||
import axios from 'axios'
|
||||
import { saveAs } from 'file-saver'
|
||||
import * as XLSX from 'xlsx'
|
||||
import { getToken } from '@/utils/auth'
|
||||
|
||||
/**
|
||||
* 解析Excel文件
|
||||
* @param {String} url - Excel文件URL
|
||||
* @returns {Promise<Array>} - 解析后的数据数组
|
||||
*/
|
||||
export function parseExcel(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios({
|
||||
method: 'get',
|
||||
url: process.env.VUE_APP_BASE_API + url,
|
||||
responseType: 'arraybuffer',
|
||||
headers: { 'Authorization': 'Bearer ' + getToken() }
|
||||
}).then(response => {
|
||||
try {
|
||||
// 使用xlsx库解析Excel数据
|
||||
const data = new Uint8Array(response.data)
|
||||
const workbook = XLSX.read(data, { type: 'array' })
|
||||
const firstSheetName = workbook.SheetNames[0]
|
||||
const worksheet = workbook.Sheets[firstSheetName]
|
||||
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 })
|
||||
resolve(jsonData)
|
||||
} catch (error) {
|
||||
reject('解析Excel失败: ' + error.message)
|
||||
}
|
||||
}).catch(error => {
|
||||
reject('下载Excel失败: ' + error.message)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 从本地文件直接读取Excel数据
|
||||
* @param {File} file - 上传的Excel文件对象
|
||||
* @returns {Promise<Array>} - 解析后的数据数组
|
||||
*/
|
||||
export function parseExcelFile(file) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
try {
|
||||
const data = new Uint8Array(e.target.result);
|
||||
const workbook = XLSX.read(data, { type: 'array' });
|
||||
const firstSheetName = workbook.SheetNames[0];
|
||||
const worksheet = workbook.Sheets[firstSheetName];
|
||||
const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });
|
||||
resolve(jsonData);
|
||||
} catch (error) {
|
||||
reject('解析Excel文件失败: ' + error.message);
|
||||
}
|
||||
};
|
||||
reader.onerror = (error) => reject('读取文件失败: ' + error);
|
||||
reader.readAsArrayBuffer(file);
|
||||
} catch (error) {
|
||||
reject('处理文件失败: ' + error.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出数据到Excel文件
|
||||
* @param {Array} data - 要导出的数据数组
|
||||
* @param {String} filename - 文件名(不含扩展名)
|
||||
* @param {String} ext - 文件扩展名,默认为.xlsx
|
||||
*/
|
||||
export function exportExcel(data, filename, ext = '.xlsx') {
|
||||
const worksheet = XLSX.utils.aoa_to_sheet(data)
|
||||
const workbook = XLSX.utils.book_new()
|
||||
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1')
|
||||
|
||||
// 确定文件类型
|
||||
let bookType = 'xlsx';
|
||||
let mimeType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
||||
|
||||
// 根据扩展名确定文件类型
|
||||
if (ext === '.xls') {
|
||||
bookType = 'xls';
|
||||
mimeType = 'application/vnd.ms-excel';
|
||||
} else if (ext === '.et') {
|
||||
// WPS表格格式,实际上仍使用xlsx格式但改变扩展名
|
||||
bookType = 'xlsx';
|
||||
mimeType = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
|
||||
}
|
||||
|
||||
// 生成Excel文件并下载
|
||||
const excelBuffer = XLSX.write(workbook, { bookType: bookType, type: 'array' })
|
||||
const blob = new Blob([excelBuffer], { type: mimeType })
|
||||
|
||||
// 确保扩展名正确
|
||||
const fileExt = ext.startsWith('.') ? ext : `.${ext}`;
|
||||
saveAs(blob, filename + fileExt)
|
||||
}
|
390
ruoyi-ui/src/views/banma/orders/index.vue
Normal file
390
ruoyi-ui/src/views/banma/orders/index.vue
Normal file
@ -0,0 +1,390 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-card class="box-card">
|
||||
<template #header>
|
||||
<div class="clearfix">
|
||||
<span class="card-title">斑马订单数据</span>
|
||||
</div>
|
||||
</template>
|
||||
<el-row :gutter="20" class="mb8">
|
||||
<el-col :span="4">
|
||||
<el-button
|
||||
type="primary"
|
||||
plain
|
||||
icon="el-icon-data-analysis"
|
||||
size="mini"
|
||||
@click="loadData"
|
||||
:loading="loading"
|
||||
class="action-button"
|
||||
>获取数据</el-button>
|
||||
</el-col>
|
||||
<el-col :span="4" v-if="orderData.length > 0">
|
||||
<el-button
|
||||
type="success"
|
||||
plain
|
||||
icon="el-icon-download"
|
||||
size="mini"
|
||||
@click="handleExport"
|
||||
class="action-button"
|
||||
>导出数据</el-button>
|
||||
</el-col>
|
||||
<el-col :span="16">
|
||||
<div class="notice-box" v-if="orderData.length > 0">
|
||||
已加载 <span class="count-number">{{orderData.length}}</span> 条订单数据
|
||||
<span v-if="loading" class="loading-text">(加载中...)</span>
|
||||
<span v-else-if="!hasMore" class="complete-text">(已加载完成)</span>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 加载进度条 -->
|
||||
<el-row v-if="loading">
|
||||
<el-col :span="24">
|
||||
<div class="progress-container">
|
||||
<el-progress
|
||||
:percentage="loadProgress.percentage"
|
||||
:format="progressFormat"
|
||||
status="primary"
|
||||
:stroke-width="12"
|
||||
class="load-progress"
|
||||
></el-progress>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<el-table v-loading="loading" :data="orderData" border style="width: 100%">
|
||||
<el-table-column label="采购单号" align="center" prop="poTrackingNumber" min-width="15%" header-align="center" />
|
||||
<el-table-column label="客户下单时间" align="center" prop="orderedAt" min-width="15%" header-align="center" />
|
||||
<el-table-column label="下单距今时间" align="center" prop="timeSinceOrder" min-width="10%" header-align="center" />
|
||||
<el-table-column label="国际运单号" align="center" prop="internationalTrackingNumber" min-width="15%" header-align="center" />
|
||||
<el-table-column label="国际运费" align="center" prop="internationalShippingFee" min-width="10%" header-align="center" />
|
||||
<el-table-column label="物流状态" align="center" prop="trackInfo" min-width="20%" header-align="center">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.trackInfo" type="success">{{ scope.row.trackInfo }}</el-tag>
|
||||
<span v-else>暂无物流信息</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 无更多数据提示 -->
|
||||
<div v-if="orderData.length > 0 && !hasMore && !loading" class="no-more-data">
|
||||
已加载全部数据
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import request from '@/utils/request'
|
||||
import { parseTime } from '@/utils/ruoyi'
|
||||
import { saveAs } from 'file-saver'
|
||||
import * as XLSX from 'xlsx'
|
||||
|
||||
export default {
|
||||
name: "BanmaOrders",
|
||||
data() {
|
||||
return {
|
||||
// 遮罩层
|
||||
loading: false,
|
||||
// 订单数据
|
||||
orderData: [],
|
||||
// 是否有更多数据
|
||||
hasMore: false,
|
||||
// 下一页页码
|
||||
nextPage: 1,
|
||||
// 总页数
|
||||
totalPages: 1,
|
||||
// 当前页数
|
||||
currentPage: 0,
|
||||
// 总记录数
|
||||
totalRecords: 0,
|
||||
// 加载进度
|
||||
loadProgress: {
|
||||
current: 0,
|
||||
total: 100,
|
||||
percentage: 0
|
||||
}
|
||||
};
|
||||
},
|
||||
created() {
|
||||
// 页面加载时检查本地缓存
|
||||
this.loadFromCache();
|
||||
},
|
||||
methods: {
|
||||
/** 从本地缓存加载数据 */
|
||||
loadFromCache() {
|
||||
try {
|
||||
const cachedData = localStorage.getItem('banma_orders_data');
|
||||
|
||||
if (cachedData) {
|
||||
const parsedData = JSON.parse(cachedData);
|
||||
this.orderData = parsedData.orderData || [];
|
||||
this.totalRecords = parsedData.totalRecords || 0;
|
||||
this.totalPages = parsedData.totalPages || 1;
|
||||
this.hasMore = parsedData.hasMore || false;
|
||||
this.nextPage = parsedData.nextPage || 1;
|
||||
this.currentPage = parsedData.currentPage || 0;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载缓存数据失败:', error);
|
||||
}
|
||||
},
|
||||
|
||||
/** 保存数据到本地缓存 */
|
||||
saveToCache() {
|
||||
try {
|
||||
if (this.orderData.length > 0) {
|
||||
const cacheData = {
|
||||
orderData: this.orderData,
|
||||
totalRecords: this.totalRecords,
|
||||
totalPages: this.totalPages,
|
||||
hasMore: this.hasMore,
|
||||
nextPage: this.nextPage,
|
||||
currentPage: this.currentPage,
|
||||
timestamp: new Date().getTime()
|
||||
};
|
||||
|
||||
localStorage.setItem('banma_orders_data', JSON.stringify(cacheData));
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
this.$message.warning('保存数据到本地缓存失败');
|
||||
}
|
||||
},
|
||||
|
||||
/** 清空缓存数据 */
|
||||
clearCache() {
|
||||
localStorage.removeItem('banma_orders_data');
|
||||
console.log('已清空本地缓存数据');
|
||||
},
|
||||
|
||||
/** 获取订单列表 */
|
||||
loadData() {
|
||||
this.loading = true;
|
||||
this.orderData = []; // 清空现有数据
|
||||
this.nextPage = 1;
|
||||
this.currentPage = 0;
|
||||
this.totalPages = 1;
|
||||
this.totalRecords = 0;
|
||||
this.loadProgress = {
|
||||
current: 0,
|
||||
total: 100,
|
||||
percentage: 0
|
||||
};
|
||||
|
||||
// 清空现有缓存
|
||||
this.clearCache();
|
||||
|
||||
// 开始自动加载数据
|
||||
this.loadFirstPage();
|
||||
},
|
||||
|
||||
/** 加载第一页数据 */
|
||||
loadFirstPage() {
|
||||
request({
|
||||
url: '/tool/banma/orders/all',
|
||||
method: 'get',
|
||||
timeout: 920000 // 增加到2分钟超时时间
|
||||
}).then(response => {
|
||||
if (response.code === 200) {
|
||||
this.orderData = response.data.orders;
|
||||
this.hasMore = response.data.hasMore;
|
||||
this.nextPage = response.data.nextPage;
|
||||
this.currentPage = 1;
|
||||
this.totalRecords = response.data.total || 0;
|
||||
this.totalPages = response.data.totalPages || 1;
|
||||
|
||||
this.updateProgress();
|
||||
this.saveToCache();
|
||||
|
||||
// 如果有更多数据,自动加载下一页
|
||||
if (this.hasMore) {
|
||||
this.autoLoadNextPage();
|
||||
} else {
|
||||
this.loading = false;
|
||||
}
|
||||
} else {
|
||||
this.loading = false;
|
||||
this.$message.error(response.msg || "获取订单失败");
|
||||
}
|
||||
}).catch(error => {
|
||||
this.loading = false;
|
||||
this.$message.error("获取订单失败: " + (error.message || error));
|
||||
});
|
||||
},
|
||||
|
||||
/** 自动加载下一页 */
|
||||
autoLoadNextPage() {
|
||||
if (!this.hasMore || !this.loading) return;
|
||||
|
||||
// 设置延迟,避免请求过于频繁
|
||||
setTimeout(() => {
|
||||
request({
|
||||
url: '/tool/banma/orders/next',
|
||||
method: 'get',
|
||||
params: { page: this.nextPage },
|
||||
timeout: 920000 // 增加到2分钟超时时间
|
||||
}).then(response => {
|
||||
if (response.code === 200) {
|
||||
// 追加数据
|
||||
this.orderData = [...this.orderData, ...response.data.orders];
|
||||
this.hasMore = response.data.hasMore;
|
||||
this.nextPage = response.data.nextPage;
|
||||
this.currentPage++;
|
||||
|
||||
// 更新进度
|
||||
this.updateProgress();
|
||||
|
||||
// 每次加载成功后更新缓存
|
||||
this.saveToCache();
|
||||
|
||||
// 进度显示
|
||||
const progress = Math.round((this.orderData.length / this.totalRecords) * 100);
|
||||
if (progress % 25 === 0 || progress > 90) { // 每加载25%或接近完成时提示
|
||||
this.$message.info(`已加载 ${progress}%,共${this.orderData.length}/${this.totalRecords}条`);
|
||||
}
|
||||
if (this.hasMore) {
|
||||
this.autoLoadNextPage();
|
||||
} else {
|
||||
this.loading = false;
|
||||
this.$message.success("所有数据加载完成");
|
||||
}
|
||||
} else {
|
||||
this.loading = false;
|
||||
this.$message.error(response.msg || "获取更多订单失败");
|
||||
}
|
||||
}).catch(error => {
|
||||
if (error.message && error.message.includes('timeout')) {
|
||||
this.$message.warning(`第${this.nextPage}页请求超时,将尝试继续下一页`);
|
||||
this.nextPage++; // 跳过当前页
|
||||
this.currentPage++;
|
||||
this.updateProgress();
|
||||
this.autoLoadNextPage(); // 继续加载下一页
|
||||
} else {
|
||||
this.loading = false;
|
||||
this.$message.error("获取更多订单失败: " + (error.message || error));
|
||||
}
|
||||
});
|
||||
}, 300); // 减少延迟时间,提高加载速度
|
||||
},
|
||||
|
||||
/** 更新进度条 */
|
||||
updateProgress() {
|
||||
let percentage;
|
||||
if (this.totalRecords > 0) {
|
||||
percentage = Math.min(Math.round((this.orderData.length / this.totalRecords) * 100), 99);
|
||||
} else {
|
||||
percentage = Math.min(Math.round((this.currentPage / this.totalPages) * 100), 99);
|
||||
}
|
||||
|
||||
this.loadProgress = {
|
||||
current: this.currentPage,
|
||||
total: this.totalPages,
|
||||
percentage: percentage
|
||||
};
|
||||
},
|
||||
|
||||
/** 进度条格式化 */
|
||||
progressFormat(percentage) {
|
||||
return `已加载 ${this.currentPage}/${this.totalPages} 页,${this.orderData.length}/${this.totalRecords} 条数据`;
|
||||
},
|
||||
|
||||
/** 导出Excel */
|
||||
handleExport() {
|
||||
if (!this.orderData.length) {
|
||||
this.$message.warning("没有数据可导出");
|
||||
return;
|
||||
}
|
||||
|
||||
// 准备主订单数据
|
||||
const mainOrderData = [
|
||||
['采购单号', '客户下单时间', '下单距今时间', '国际运单号', '国际运费', '物流状态']
|
||||
];
|
||||
|
||||
// 添加主订单数据行
|
||||
this.orderData.forEach(item => {
|
||||
mainOrderData.push([
|
||||
item.poTrackingNumber || '',
|
||||
item.orderedAt || '',
|
||||
item.timeSinceOrder || '',
|
||||
item.internationalTrackingNumber || '',
|
||||
item.internationalShippingFee || '',
|
||||
item.trackInfo || '暂无物流信息'
|
||||
]);
|
||||
});
|
||||
|
||||
// 创建工作簿
|
||||
const wb = XLSX.utils.book_new();
|
||||
|
||||
|
||||
const wsMain = XLSX.utils.aoa_to_sheet(mainOrderData);
|
||||
XLSX.utils.book_append_sheet(wb, wsMain, '订单数据');
|
||||
const excelBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' });
|
||||
const blob = new Blob([excelBuffer], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
||||
saveAs(blob, `斑马订单数据_${parseTime(new Date())}.xlsx`);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.mb8 {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.progress-container {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
background-color: #f5f7fa;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.action-button {
|
||||
width: 100%;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.load-progress {
|
||||
margin-top: 5px;
|
||||
}
|
||||
.el-table {
|
||||
margin-top: 15px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
.el-table :deep(th) {
|
||||
background-color: #f5f7fa !important;
|
||||
color: #303133;
|
||||
font-weight: bold;
|
||||
padding: 10px 0;
|
||||
}
|
||||
.card-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
}
|
||||
.box-card {
|
||||
margin-bottom: 20px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.notice-box {
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
}
|
||||
.count-number {
|
||||
font-weight: bold;
|
||||
color: #409EFF;
|
||||
}
|
||||
.loading-text {
|
||||
color: #E6A23C;
|
||||
font-style: italic;
|
||||
}
|
||||
.complete-text {
|
||||
color: #67C23A;
|
||||
font-style: italic;
|
||||
}
|
||||
.no-more-data {
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
margin: 20px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
@ -87,7 +87,7 @@
|
||||
<s> 满104748341 </s> <s> 满160110482 </s> <s> 满170801498 </s> <s> 满108482800 </s>
|
||||
<s> 满101046199 </s> <s> 满136919097 </s> <s> 满143961921 </s> <s> 满174951577 </s>
|
||||
<s> 满161281055 </s> <s> 满138988063 </s> <s> 满151450850 </s> <s> 满224622315 </s>
|
||||
<s> 满287842588 </s> <s> 满187944233 </s> <a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=G6r5KGCaa3pqdbUSXNIgYloyb8e0_L0D&authKey=4w8tF1eGW7%2FedWn%2FHAypQksdrML%2BDHolQSx7094Agm7Luakj9EbfPnSTxSi2T1LQ&noverify=0&group_code=228578329" target="_blank">228578329</a>
|
||||
<s> 满287842588 </s> <s> 满187944233 </s> <s> 满228578329 </s> <a href="http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=GsOo-OLz53J8y_9TPoO6XXSGNRTgbFxA&authKey=R7Uy%2Feq%2BZsoKNqHvRKhiXpypW7DAogoWapOawUGHokJSBIBIre2%2FoiAZeZBSLuBc&noverify=0&group_code=191164766" target="_blank">191164766</a>
|
||||
</p>
|
||||
<p>
|
||||
<i class="el-icon-chat-dot-round"></i> 微信:<a
|
||||
@ -111,6 +111,42 @@
|
||||
<span>更新日志</span>
|
||||
</div>
|
||||
<el-collapse accordion>
|
||||
<el-collapse-item title="v3.9.0 - 2025-05-28">
|
||||
<ol>
|
||||
<li>优化菜单搜索查询页</li>
|
||||
<li>导航栏显示昵称&设置</li>
|
||||
<li>菜单管理新增路由名称</li>
|
||||
<li>添加底部版权信息&开关</li>
|
||||
<li>分配角色禁用不允许勾选</li>
|
||||
<li>Excel导入导出支持多图片</li>
|
||||
<li>添加页签图标显示开关功能</li>
|
||||
<li>上传组件新增拖动排序属性</li>
|
||||
<li>显隐列组件支持全选/全不选</li>
|
||||
<li>初始密码支持自定义修改策略</li>
|
||||
<li>账号密码支持自定义更新周期</li>
|
||||
<li>代码生成列表支持按时间排序</li>
|
||||
<li>支持富文本复制粘贴图片上传至url</li>
|
||||
<li>支持文件&图片组件自定义地址&参数</li>
|
||||
<li>升级tomcat到最新版本9.0.105</li>
|
||||
<li>升级oshi到最新版本6.8.1</li>
|
||||
<li>升级fastjson到最新版2.0.57</li>
|
||||
<li>升级commons.io到最新版本2.19.0</li>
|
||||
<li>package.json移除runjs依赖</li>
|
||||
<li>package.json移除eslint依赖</li>
|
||||
<li>package.json移除vue-meta依赖</li>
|
||||
<li>修复代码生成主子表校验必填失效问题</li>
|
||||
<li>优化前端树结构性能问题</li>
|
||||
<li>优化前端处理路由函数代码</li>
|
||||
<li>优化文件上传组件新增类型</li>
|
||||
<li>优化顶部菜单搜索栏为多层级显示</li>
|
||||
<li>优化文件&图片上传组件新增disabled属性</li>
|
||||
<li>优化空指针异常时无法获取错误信息问题</li>
|
||||
<li>优化定时任务字符包含多个括号导致数据错误</li>
|
||||
<li>优化登录&注册页表头使用VUE_APP_TITLE配置值</li>
|
||||
<li>优化导出Excel日期格式双击离开后与设定的格式不一致问题</li>
|
||||
<li>其他细节优化</li>
|
||||
</ol>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item title="v3.8.9 - 2024-12-30">
|
||||
<ol>
|
||||
<li>用户管理支持分栏拖动</li>
|
||||
@ -1023,7 +1059,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
// 版本号
|
||||
version: "3.8.9"
|
||||
version: "3.9.0"
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
1075
ruoyi-ui/src/views/prod/products/index.vue
Normal file
1075
ruoyi-ui/src/views/prod/products/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
@ -9,7 +9,7 @@ const CompressionPlugin = require('compression-webpack-plugin')
|
||||
|
||||
const name = process.env.VUE_APP_TITLE || '若依管理系统' // 网页标题
|
||||
|
||||
const baseUrl = 'http://localhost:8080' // 后端接口
|
||||
const baseUrl = 'http://192.168.1.89:8080' // 后端接口
|
||||
|
||||
const port = process.env.port || process.env.npm_config_port || 80 // 端口
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user