Browse Source

first version

master
许孟阳 2 years ago
commit
4ac653bc58
  1. 7
      java/.gitignore
  2. 9
      java/application.properties
  3. 46
      java/logback.xml
  4. 99
      java/pom.xml
  5. 35
      java/src/main/java/vip/xumy/cube/CubeRescueApplocation.java
  6. 20
      java/src/main/java/vip/xumy/cube/conf/CubeRescueInitializer.java
  7. 91
      java/src/main/java/vip/xumy/cube/ctrl/CubeController.java
  8. 79
      java/src/main/java/vip/xumy/cube/mapper/DataCubeMapper.java
  9. 26
      java/src/main/java/vip/xumy/cube/mapper/SqlExcuter.java
  10. 125
      java/src/main/java/vip/xumy/cube/pojo/Cube4Mysql.java
  11. 48
      java/src/main/java/vip/xumy/cube/pojo/Dimension.java
  12. 52
      java/src/main/java/vip/xumy/cube/pojo/PageParam.java
  13. 140
      java/src/main/java/vip/xumy/cube/service/DataCubeService.java
  14. 35
      java/src/main/resources/application.properties
  15. 53
      java/src/test/java/com/sprt/g5s/test/CubeUtilTester.java
  16. 2
      vue/.env.development
  17. 2
      vue/.env.production
  18. 23
      vue/.gitignore
  19. 24
      vue/README.md
  20. 5
      vue/babel.config.js
  21. 50
      vue/package.json
  22. BIN
      vue/public/background.jpg
  23. BIN
      vue/public/favicon.ico
  24. 17
      vue/public/index.html
  25. 10
      vue/public/webConfig.json
  26. 102
      vue/src/App.vue
  27. BIN
      vue/src/assets/logo.png
  28. 172
      vue/src/assets/style/base.scss
  29. 24
      vue/src/assets/style/conf.scss
  30. 13
      vue/src/assets/style/dark.scss
  31. 197
      vue/src/assets/style/element.scss
  32. 0
      vue/src/components/5gs/profile/index.vue
  33. 33
      vue/src/components/5gs/profile/slice.vue
  34. 15
      vue/src/components/main/aside.vue
  35. 15
      vue/src/components/main/header.vue
  36. 5
      vue/src/components/template/index.ts
  37. 76
      vue/src/components/template/listTable.vue
  38. 69
      vue/src/components/template/modifyForm.vue
  39. 70
      vue/src/components/template/searchRow.vue
  40. 29
      vue/src/config/apiUrl.ts
  41. 257
      vue/src/config/axios.ts
  42. 88
      vue/src/config/element.ts
  43. 90
      vue/src/config/menu.ts
  44. 18
      vue/src/main.ts
  45. 31
      vue/src/plugs/dict/5gs.ts
  46. 11
      vue/src/plugs/dict/base.ts
  47. 94
      vue/src/plugs/dict/device.ts
  48. 6
      vue/src/plugs/dict/index.ts
  49. 45
      vue/src/plugs/formatter.ts
  50. 119
      vue/src/plugs/validate/device.ts
  51. 2
      vue/src/plugs/validate/main.ts
  52. 11
      vue/src/plugs/validate/user.ts
  53. 26
      vue/src/router/index.ts
  54. 6
      vue/src/shims-vue.d.ts
  55. 19
      vue/src/store/actions.ts
  56. 12
      vue/src/store/index.ts
  57. 47
      vue/src/store/mutations.ts
  58. 15
      vue/src/store/state.ts
  59. 139
      vue/src/views/admin/cube.vue
  60. 67
      vue/src/views/admin/cubeData.vue
  61. 150
      vue/src/views/admin/cubeDetail.vue
  62. 129
      vue/src/views/main/login.vue
  63. 42
      vue/tsconfig.json
  64. 17
      vue/vue.config.js
  65. 5867
      vue/yarn.lock

7
java/.gitignore vendored

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
/.classpath
/.project
/.settings
/bin/
/logs
/target
/src/main/resources/static

9
java/application.properties

@ -0,0 +1,9 @@ @@ -0,0 +1,9 @@
server.port=80
server.servlet.context-path=/
dev=on
# mysql数据源配置
spring.datasource.url=jdbc:mysql://cluster3.xumy.vip:3306/test?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=123456

46
java/logback.xml

@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="D:/logs/cube" />
<!--控制台日志, 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度,%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!--文件日志, 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/admin-%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>10MB</MaxFileSize>
</triggeringPolicy>
</appender>
<logger name="vip.xumy" level="DEBUG"/>
<!--myibatis log configure-->
<logger name="com.apache.ibatis" level="TRACE"/>
<logger name="java.sql.Connection" level="DEBUG"/>
<logger name="java.sql.Statement" level="DEBUG"/>
<logger name="java.sql.PreparedStatement" level="DEBUG"/>
<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE"/>
</root>
</configuration>

99
java/pom.xml

@ -0,0 +1,99 @@ @@ -0,0 +1,99 @@
<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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.1</version>
</parent>
<groupId>vip.xumy.cube</groupId>
<artifactId>xumy_cube</artifactId>
<version>1.0.0</version>
<name>xumy_cube</name>
<description>data cube</description>
<packaging>jar</packaging>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<core.version>1.0.0</core.version>
<log4j.version>1.2.17</log4j.version>
<fastjson.version>1.2.46</fastjson.version>
<druid.version>1.1.9</druid.version>
<mybatis-plus.version>3.5.1</mybatis-plus.version>
<mybatis.pagehelper.version>1.4.6</mybatis.pagehelper.version>
</properties>
<dependencies>
<dependency>
<groupId>vip.xumy.core</groupId>
<artifactId>xumy_core</artifactId>
<version>${core.version}</version>
</dependency>
<!-- 代码修改之后可以实时生效,该模块在完整的打包环境下运行的时候会被禁用 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--junit 测试包-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- alibaba的druid数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- mybatis plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- mybatis的pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>${mybatis.pagehelper.version}</version>
</dependency>
</dependencies>
<!-- Package as an executable jar -->
<build>
<finalName>dataCube</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
</configuration>
</plugin>
</plugins>
</build>
</project>

35
java/src/main/java/vip/xumy/cube/CubeRescueApplocation.java

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
package vip.xumy.cube;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import vip.xumy.cube.conf.CubeRescueInitializer;
public class CubeRescueApplocation extends SpringBootServletInitializer {
public static void main(String[] args) {
Class<?>[] arr = new Class<?>[] {CubeRescueInitializer.class};
SpringApplication.run(arr,args);
}
/**
* 分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.H2));
return interceptor;
}
}

20
java/src/main/java/vip/xumy/cube/conf/CubeRescueInitializer.java

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
package vip.xumy.cube.conf;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.ComponentScan;
@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
@ComponentScan(value = "vip.xumy.cube, vip.xumy.core")
@MapperScan({"vip.xumy.cube.mapper"})
public class CubeRescueInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(CubeRescueInitializer.class);
}
}

91
java/src/main/java/vip/xumy/cube/ctrl/CubeController.java

@ -0,0 +1,91 @@ @@ -0,0 +1,91 @@
package vip.xumy.cube.ctrl;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.github.pagehelper.Page;
import vip.xumy.core.pojo.com.BaseResponse;
import vip.xumy.core.pojo.com.PageResponse;
import vip.xumy.cube.pojo.Cube4Mysql;
import vip.xumy.cube.pojo.Dimension;
import vip.xumy.cube.service.DataCubeService;
/**
* Ownership belongs to the company
*
* @author:mengyxu
* @date:2023年3月10日
*/
@RestController
@RequestMapping("cube")
public class CubeController {
@Autowired
private DataCubeService cubeService;
@GetMapping
public BaseResponse list(Cube4Mysql example) {
return new BaseResponse(cubeService.list(example));
}
@GetMapping("{name}")
public BaseResponse getCube(@PathVariable("name") String name) {
return new BaseResponse(cubeService.get(name));
}
@PostMapping
public BaseResponse save(@RequestBody Cube4Mysql cube) {
cubeService.save(cube);
return new BaseResponse(true, "添加立方体成功!");
}
@PostMapping("dimension")
public BaseResponse saveDimesion(@RequestBody Dimension dimension) {
cubeService.save(dimension);
return new BaseResponse(true, "添加纬度成功!");
}
@PutMapping("init")
public BaseResponse initCube(@RequestBody String name) {
cubeService.init(name);
return new BaseResponse(true, "初始化立方体成功!");
}
@PostMapping("assemble")
public BaseResponse assembleCube(@RequestBody String name) {
cubeService.assemble(name);
return new BaseResponse(true, "构建任务提交成功!");
}
@DeleteMapping
public BaseResponse delete(String name) {
cubeService.delete(name);
return new BaseResponse(true, "删除立方体成功!");
}
@DeleteMapping("dimension")
public BaseResponse delete(Dimension dimension) {
cubeService.delete(dimension);
return new BaseResponse(true, "删除维度成功!");
}
@PutMapping("select")
public BaseResponse selectCube(@RequestBody Cube4Mysql cube) {
Page<Object> page = cube.startPage();
List<Map<String, Object>> list = cubeService.select(cube);
PageResponse<Map<String, Object>> result = new PageResponse<>(list, page.getTotal());
return new BaseResponse(result);
}
}

79
java/src/main/java/vip/xumy/cube/mapper/DataCubeMapper.java

@ -0,0 +1,79 @@ @@ -0,0 +1,79 @@
package vip.xumy.cube.mapper;
import java.util.List;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
import vip.xumy.cube.pojo.Cube4Mysql;
import vip.xumy.cube.pojo.Dimension;
/**
* Ownership belongs to the company
*
* @author:mengyxu
* @date:2023年3月10日
*/
@Mapper
public interface DataCubeMapper {
@Select("""
<script>
SELECT * FROM cube
<where>
<if test="name != null"> AND name = #{name} </if>
<if test="source != null"> AND source = #{source} </if>
<if test="status != null"> AND status = #{status} </if>
</where>
ORDER BY create_time DESC
</script>
""")
List<Cube4Mysql> list(Cube4Mysql example);
@Select("""
<script>
SELECT * FROM cube_dimession
<where>
<if test="cubeName != null"> AND cube_name = #{cubeName} </if>
<if test="type != null"> AND type = #{type} </if>
</where>
</script>
""")
List<Dimension> dimesions(@Param("cubeName") String cubeName, @Param("type") Integer type);
@Update("""
<script>
UPDATE cube
<set>
<if test="status != null"> status = #{status}, </if>
assemble_time = #{assembleTime},
<if test="total != null"> total = #{total}, </if>
</set>
WHERE name = #{name}
</script>
""")
void update(Cube4Mysql cube);
@Insert("INSERT INTO cube VALUES (#{name}, #{source}, #{sourceTimer}, #{remark}, #{status}, #{createTime}, #{assembleTime}, #{total})")
void insert(Cube4Mysql cube);
@Insert("INSERT INTO cube_dimession VALUES (#{cubeName}, #{column}, #{dimesion}, #{remark}, #{type}, #{dataType}, #{length})")
void insertDimession(Dimension dimesion);
@Delete("""
DELETE FROM cube WHERE name = #{name};
DELETE FROM cube_dimession WHERE cubeName = #{name};
""")
void delete(String name);
@Delete("""
DELETE FROM cube_dimession WHERE cubeName = #{name} AND column = #{column};
""")
void deleteDimension(Dimension dimension);
}

26
java/src/main/java/vip/xumy/cube/mapper/SqlExcuter.java

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
package vip.xumy.cube.mapper;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
/**
* Ownership belongs to the company
*
* @author:mengyxu
* @date:2023年3月10日
*/
@Mapper
public interface SqlExcuter {
@Insert("${sql}")
int excute(String sql);
@Select("${sql}")
List<Map<String, Object>> select(String sql);
}

125
java/src/main/java/vip/xumy/cube/pojo/Cube4Mysql.java

@ -0,0 +1,125 @@ @@ -0,0 +1,125 @@
package vip.xumy.cube.pojo;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.alibaba.fastjson.annotation.JSONField;
import lombok.Getter;
import lombok.Setter;
import vip.xumy.core.utils.CombineUtil;
import vip.xumy.core.utils.DateUtil;
import vip.xumy.core.utils.StringUtil;
/**
* Ownership belongs to the company
*
* @author:mengyxu
* @date:2023年3月9日
*/
@Setter
@Getter
public class Cube4Mysql extends PageParam {
private String scheme = "cube";
private List<Dimension> dimensions;
private List<Dimension> data;
private String name;
private String source;
private String sourceTimer;
private String remark;
private Integer status;
private String createTime;
private String assembleTime;
private Integer total;
@JSONField(serialize = false)
private String curTime;
public String destorySql() {
StringBuilder sb = new StringBuilder("DROP TABLE IF EXISTS ").append("`").append(name).append("`;");
return sb.toString();
}
public String initSql() {
StringBuilder sb = new StringBuilder(destorySql()).append(" CREATE TABLE ").append("`").append(name)
.append("` (`id` int(11) NOT NULL AUTO_INCREMENT");
for (Dimension dimesion : dimensions) {
sb.append(", `").append(dimesion.getColumn()).append("` ");
switch (dimesion.getDataType()) {
case 0:
sb.append("varchar(").append(dimesion.getLength()).append(") ");
break;
case 1:
sb.append("int(11) ");
break;
case 2:
sb.append("datetime ");
break;
}
sb.append("DEFAULT NULL");
}
for (Dimension dimesion : data) {
sb.append(", `").append(dimesion.getColumn()).append("` int(11) NOT NULL");
}
sb.append(", PRIMARY KEY (`id`), UNIQUE KEY `base_index` (`");
List<String> columns = dimensions.stream().map(x -> x.getColumn()).collect(Collectors.toList());
sb.append(StringUtil.join(columns, "`,`")).append("`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;");
return sb.toString();
}
public String assembleSql() {
curTime = DateUtil.format(new Date(), DateUtil.FORMAT19_LINE_YYYYMMDDHHMMSS);
List<String> columns = Stream.of(dimensions, data).flatMap(x -> x.stream()).map(x -> "`" + x.getColumn() + "`")
.collect(Collectors.toList());
StringBuilder sb = new StringBuilder("INSERT INTO `").append(name).append("` (")
.append(StringUtil.join(columns)).append(") ");
new ArrayList<>(dimensions).addAll(data);
List<List<Integer>> combine = CombineUtil.combine(dimensions.size());
List<String> combineSql = combine.stream().map(x -> assembleOneCombination(x)).collect(Collectors.toList());
sb.append(StringUtil.join(combineSql.toArray(new String[0]), " UNION ")).append(";");
return sb.toString();
}
public String assembleOneCombination(List<Integer> list) {
StringBuilder sb = new StringBuilder("SELECT ");
for (int i = 0; i < dimensions.size(); i++) {
if (list.contains(i + 1)) {
sb.append(dimensions.get(i).getDimesion());
} else {
sb.append("null");
}
sb.append(",");
}
List<String> sumArr = data.stream().map(x -> x.getDimesion()).collect(Collectors.toList());
List<String> array = list.stream().map(x -> dimensions.get(x - 1).getDimesion()).collect(Collectors.toList());
sb.append(StringUtil.join(sumArr)).append(" FROM `").append(source).append("`");
if (!StringUtil.isEmpty(sourceTimer)) {
sb.append(" WHERE `").append(sourceTimer).append("` <= '").append(curTime).append("'");
if (assembleTime != null) {
sb.append(" AND `").append(sourceTimer).append("` > '").append(assembleTime).append("'");
}
}
sb.append(" GROUP BY ").append(StringUtil.join(array));
return sb.toString();
}
public String selectSql() {
StringBuilder sb = new StringBuilder("SELECT * FROM `").append(name).append("` WHERE 1=1");
for (Dimension dimesion : dimensions) {
if (!dimesion.isChecked()) {
sb.append(" AND `").append(dimesion.getColumn()).append("` IS NULL");
} else if (StringUtil.isEmpty(dimesion.getValue())) {
sb.append(" AND `").append(dimesion.getColumn()).append("` IS NOT NULL");
} else {
sb.append(" AND `").append(dimesion.getColumn()).append("` = ").append(dimesion.getValue());
}
}
return sb.toString();
}
}

48
java/src/main/java/vip/xumy/cube/pojo/Dimension.java

@ -0,0 +1,48 @@ @@ -0,0 +1,48 @@
package vip.xumy.cube.pojo;
import lombok.Getter;
import lombok.Setter;
/**
* Ownership belongs to the company
*
* @author:mengyxu
* @date:2023年3月10日
*/
@Setter
@Getter
public class Dimension {
private String scheme = "dimension";
private String cubeName;
private String column;
private String dimesion;
private String remark;
private Integer type;
private Integer dataType;
private Integer length;
private boolean checked = true;
private String value;
public Dimension(String srouce, String column) {
super();
this.dimesion = srouce;
this.column = column;
this.dataType = 1;
}
public Dimension(String srouce, String column, Integer dataType, Integer length) {
super();
this.dimesion = srouce;
this.column = column;
this.dataType = dataType;
this.length = length;
}
public Dimension() {
super();
}
}

52
java/src/main/java/vip/xumy/cube/pojo/PageParam.java

@ -0,0 +1,52 @@ @@ -0,0 +1,52 @@
package vip.xumy.cube.pojo;
import com.alibaba.fastjson.annotation.JSONField;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.pagehelper.PageHelper;
import lombok.Setter;
import vip.xumy.core.golbal.GlobalConstant;
/**
* Ownership belongs to the company
*
* @author:mengyxu
* @date:2022年7月21日
*/
@Setter
public class PageParam {
@TableField(exist = false)
private Integer page;
@TableField(exist = false)
private Integer size;
@JSONField(serialize = false)
public <T> Page<T> getPage() {
if (page == null) {
page = 1;
}
if (size == null) {
size = GlobalConstant.DEF_SIZE;
} else if (size > GlobalConstant.MAX_SIZE) {
size = GlobalConstant.MAX_SIZE;
}
return new Page<>(page, size);
}
@JSONField(serialize = false)
public <T> com.github.pagehelper.Page<T> startPage() {
if (page == null) {
page = 1;
}
if (size == null) {
size = GlobalConstant.DEF_SIZE;
} else if (size > GlobalConstant.MAX_SIZE) {
size = GlobalConstant.MAX_SIZE;
}
return PageHelper.startPage(page, size);
}
}

140
java/src/main/java/vip/xumy/cube/service/DataCubeService.java

@ -0,0 +1,140 @@ @@ -0,0 +1,140 @@
package vip.xumy.cube.service;
import java.util.Date;
import java.util.List;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import lombok.extern.log4j.Log4j2;
import vip.xumy.core.exception.CoreException;
import vip.xumy.core.utils.DateUtil;
import vip.xumy.core.utils.StringUtil;
import vip.xumy.cube.mapper.DataCubeMapper;
import vip.xumy.cube.mapper.SqlExcuter;
import vip.xumy.cube.pojo.Cube4Mysql;
import vip.xumy.cube.pojo.Dimension;
/**
* Ownership belongs to the company
*
* @author:mengyxu
* @date:2023年3月10日
*/
@Log4j2
@Service
@Transactional
public class DataCubeService {
@Autowired
private DataCubeMapper cubeMapper;
@Autowired
private SqlExcuter sqlExcuter;
public List<Cube4Mysql> list(Cube4Mysql example) {
return cubeMapper.list(example);
}
public void save(Cube4Mysql cube) {
if (StringUtil.isEmpty(cube.getName(), cube.getSource())) {
throw new CoreException("立方体表名称和立方体数据源表名称不能为空");
}
cube.setStatus(0);
cube.setCreateTime(DateUtil.format(new Date(), DateUtil.FORMAT19_LINE_YYYYMMDDHHMMSS));
cube.setAssembleTime(null);
cube.setTotal(0);
cubeMapper.insert(cube);
}
public void save(Dimension dimension) {
if (StringUtil.isEmpty(dimension.getCubeName(), dimension.getColumn(), dimension.getDimesion())) {
throw new CoreException("立方体表名,维度字段名和数据源表字段名均不能为空");
}
if (dimension.getType() == null || dimension.getDataType() == null) {
throw new CoreException("纬度类型和数据类型均不能为空");
}
cubeMapper.insertDimession(dimension);
reset(dimension.getCubeName());
}
public void init(String cubeName) {
Cube4Mysql cube = get(cubeName);
if (cube.getDimensions() == null || cube.getDimensions().isEmpty()) {
throw new CoreException("该立方体未添加任何维度,无法完成初始化");
}
String initSql = cube.initSql();
cube.setStatus(1);
cube.setTotal(0);
cube.setAssembleTime(null);
sqlExcuter.excute(initSql);
cubeMapper.update(cube);
}
public void assemble(String name) {
Cube4Mysql cube = get(name);
if (cube.getStatus() == 0) {
throw new CoreException("该立方体尚未初始化");
}
if (cube.getStatus() == 2) {
throw new CoreException("该立方体正在构建中");
}
cube.setStatus(2);
cubeMapper.update(cube);
new Thread(() -> {
try {
String assembleSql = cube.assembleSql();
int total = sqlExcuter.excute(assembleSql);
cube.setAssembleTime(cube.getCurTime());
cube.setTotal(cube.getTotal() + total);
cube.setStatus(1);
} catch (Exception e) {
log.error("立方体:" + name + "构建失败", e);
cube.setStatus(3);
}
cubeMapper.update(cube);
}).start();
}
public Cube4Mysql get(String name) {
Cube4Mysql example = new Cube4Mysql();
example.setName(name);
List<Cube4Mysql> list = cubeMapper.list(example);
if (list == null || list.isEmpty()) {
return null;
}
Cube4Mysql cube = list.get(0);
cube.setDimensions(cubeMapper.dimesions(name, 0));
cube.setData(cubeMapper.dimesions(name, 1));
return cube;
}
public void delete(String name) {
Cube4Mysql cube = get(name);
cubeMapper.delete(name);
String destorySql = cube.destorySql();
sqlExcuter.excute(destorySql);
}
public List<Map<String, Object>> select(Cube4Mysql cube) {
String selectSql = cube.selectSql();
return sqlExcuter.select(selectSql);
}
public void delete(Dimension dimension) {
cubeMapper.deleteDimension(dimension);
reset(dimension.getCubeName());
}
private void reset(String name) {
Cube4Mysql cube = get(name);
if (cube == null) {
throw new CoreException("立方体不存在");
}
cube.setStatus(0);
cubeMapper.update(cube);
}
}

35
java/src/main/resources/application.properties

@ -0,0 +1,35 @@ @@ -0,0 +1,35 @@
logging.config:logback.xml
spring.main.banner-mode=off
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.initialSize=1
spring.datasource.minIdle=1
spring.datasource.maxActive=20
spring.datasource.maxWait=60000
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.datasource.validationQuery=SELECT 1 FROM DUAL
spring.datasource.keepAlive=true
spring.datasource.testWhileIdle=true
spring.datasource.testOnBorrow=false
spring.datasource.testOnReturn=false
spring.datasource.poolPreparedStatements=false
mybatis.configuration.map-underscore-to-camel-case=true
quartz.cron.clean.global=0 */10 * * * ?
pagehelper.helperDialect=mysql
pagehelper.reasonable=true
pagehelper.supportMethodsArguments=true
pagehelper.params=count=countSql
pagehelper.returnPageInfo=check
#springdoc.swagger-ui.path=/swagger-ui.html
#springdoc.swagger-ui.enabled=true
#springdoc.api-docs.path=/api-docs
#springdoc.api-docs.enabled=true
#springdoc.packages-to-scand=com.sprt

53
java/src/test/java/com/sprt/g5s/test/CubeUtilTester.java

@ -0,0 +1,53 @@ @@ -0,0 +1,53 @@
package com.sprt.g5s.test;
import java.util.ArrayList;
import java.util.List;
import org.junit.jupiter.api.Test;
import vip.xumy.core.utils.CombineUtil;
import vip.xumy.cube.pojo.Cube4Mysql;
import vip.xumy.cube.pojo.Dimension;
/**
* Ownership belongs to the company
*
* @author:mengyxu
* @date:2023年3月9日
*/
public class CubeUtilTester {
@Test
public void combineTester() {
List<List<Integer>> combine = CombineUtil.combine(4, 2);
System.out.println(combine);
List<List<Integer>> combine1 = CombineUtil.combine(4);
System.out.println(combine1);
}
@Test
public void CubeTester() {
List<Dimension> dimensions = new ArrayList<>();
dimensions.add(new Dimension("imsi", "imsi", 0, 15));
dimensions.add(new Dimension("app", "app", 0, 32));
dimensions.add(new Dimension("apptype", "app_type", 0, 32));
dimensions.add(new Dimension("taskid", "task", 1, 11));
dimensions.add(new Dimension("FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(timestamp)/300)*300)", "time", 0, 20));
List<Dimension> sums = new ArrayList<>();
sums.add(new Dimension("SUM(toptraffic)", "up"));
sums.add(new Dimension("SUM(downtraffic)", "down"));
String srcTable = "trafficstatisticslog";
String desTable = "trafficstatisticslog_loap";
Cube4Mysql cube = new Cube4Mysql();
cube.setName(desTable);
cube.setSource(srcTable);
cube.setSourceTimer("timestamp");
cube.setDimensions(dimensions);
cube.setData(sums);
System.out.println(cube.initSql());
System.out.println(cube.assembleSql());
}
}

2
vue/.env.development

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
NODE_ENV="preview"
VUE_APP_BASE_URL="http://localhost"

2
vue/.env.production

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
NODE_ENV="production"
VUE_APP_BASE_URL=""

23
vue/.gitignore vendored

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

24
vue/README.md

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
# lz_vue
## Project setup
```
yarn install
```
### Compiles and hot-reloads for development
```
yarn serve
```
### Compiles and minifies for production
```
yarn build
```
### Lints and fixes files
```
yarn lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

5
vue/babel.config.js

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}

50
vue/package.json

@ -0,0 +1,50 @@ @@ -0,0 +1,50 @@
{
"name": "lz_vue",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^0.19.2",
"core-js": "^3.8.3",
"element-plus": "2.2.10",
"js-md5": "^0.7.3",
"vue": "^3.2.13",
"vue-class-component": "^8.0.0-0",
"vue-router": "^4.0.3",
"vuex": "^4.0.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-typescript": "~5.0.0",
"@vue/cli-plugin-vuex": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"sass": "^1.32.7",
"sass-loader": "^12.0.0",
"typescript": "~4.5.5"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"@vue/typescript/recommended"
],
"parserOptions": {
"ecmaVersion": 2020
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
}

BIN
vue/public/background.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 KiB

BIN
vue/public/favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

17
vue/public/index.html

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

10
vue/public/webConfig.json

@ -0,0 +1,10 @@ @@ -0,0 +1,10 @@
{
"web_title_": "系统统一标题",
"web_title": "LTE无线系统",
"login_form_logo_show_": "登录页面是否显示logo标志",
"login_form_logo_show": false,
"home_logo_type_": "主页logo类型,1-标题文字,!1-图片",
"home_logo_type": 1,
"web_version_": "",
"web_version": "3.0.0"
}

102
vue/src/App.vue

@ -0,0 +1,102 @@ @@ -0,0 +1,102 @@
<template>
<el-container :style="appMain">
<el-header class="app-head" :height="$store.state.sizes.headHeight">
<Header v-show="$store.state.showHeader" ref="header" />
</el-header>
<el-container :style="{ height: $store.state.sizes.mainHeight }">
<el-aside :width="$store.state.sizes.asideWidth">
<Aside v-show="$store.state.showAside"></Aside>
</el-aside>
<el-main class="app-main">
<router-view />
</el-main>
</el-container>
</el-container>
</template>
<script>
// <script lang="ts" steup>
// import { reactive, onMounted } from "vue";
// import { useStore } from "vuex";
import Header from "@/components/main/header";
import Aside from "@/components/main/aside";
import { publik } from "@/config/apiUrl";
// const store = useStore();
// const appMain = reactive({
// height: "",
// backgroundImage: "url(" + publik.background + ")",
// });
// onMounted(() => {
// store.commit("updateState", { prop: "showHeader", value: false });
// store.commit("updateState", { prop: "showAside", value: false });
// store.commit("initSizes");
// store.dispatch("getWebConfig");
// appMain.height = store.state.sizes.height;
// });
import { Options, Vue } from "vue-class-component";
@Options({
components: {
Header,
Aside
},
})
export default class app extends Vue {
appMain = {
// backgroundImage: "url(" + publik.background + ")"
}
init () {
this.$store.commit("setUserInfo", { type: 4 });
this.$store.commit("initSizes");
// this.$store.dispatch("getWebConfig");
// this.$router.push("/")
}
created () {
// - 访this
this.init();
this.appMain = {
height: this.$store.state.sizes.height,
// backgroundImage: "url(" + publik.background + ")"
};
}
mounted () {
// - 访DOM
}
}
</script>
<style lang='scss'>
@charset "UTF-8";
// @import "./assets/style/element.scss";
@import "./assets/style/base.scss";
@import "./assets/style/dark.scss";
.app-main {
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
.el-header,
.main-head,
.main-table {
padding: 0;
}
#app {
background-color: #edf2f7;
}
.app-head {
padding: 0px !important;
}
.app-main {
padding: 0px !important;
}
</style>

BIN
vue/src/assets/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

172
vue/src/assets/style/base.scss

@ -0,0 +1,172 @@ @@ -0,0 +1,172 @@
* {
margin: 0;
padding: 0;
}
html,
body {
min-width: 1200px;
font-size: 14px;
font-family: 'Microsoft YaHei';
width: 100%;
height: 100%;
overflow: hidden;
}
input {
font-family: 'Microsoft YaHei';
}
.clearfix {
&:before,
&:after {
display: table;
content: '';
}
&:after {
clear: both;
}
}
a {
&:link,
&:visited {
all: inherit;
}
&:hover {
all: inherit;
}
}
/*
* 侦码侦听设备红绿灯显示
*/
.grid-code-dev {
border-radius: 4px;
height: 4em;
}
.code-dev {
margin-right: 3%;
height: 100%;
float: right;
}
// 公共样式文件
// 带阴影的表格外的div
div.table-warpper {
padding: 0px;
margin: 0px;
border: 1px solid #20a0ff;
overflow: hidden;
// box-shadow:
// -10px 0 10px #C1C1C1, /*左边阴影*/
// 10px 0 10px #C1C1C1, /*右边阴影*/
// 0 -10px 10px #C1C1C1, /*顶部阴影*/
// 0 10px 10px #C1C1C1; /*底边阴影*/
}
.tabs-header {
margin-left: 1em;
}
.cur-table-warpper {
margin-left: -1em;
}
// input[type=number]在ff和chrome中会出现上下的小三角箭头
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none !important;
}
input[type='number'] {
-moz-appearance: textfield;
}
// 修改element
.el-picker-panel__link-btn {
display: none;
}
.el-pagination__editor {
width: 55px !important;
}
.search-row {
padding-bottom: 0.2em;
}
.form-item-btn {
float: right;
width: 130px;
}
.full-item-form .el-select,
.full-item-form .el-date-editor,
.full-item-form .el-autocomplete {
width: 100% !important;
}
.search-row {
height: 40px;
// background-color: $main-color;
padding: 5px 15px;
}
.query-mini {
width: 100px !important;
margin-right: 10px;
}
.query-smart {
width: 120px !important;
margin-right: 10px;
}
.query-short {
width: 150px !important;
margin-right: 10px;
}
.query-medium {
width: 200px !important;
margin-right: 10px;
}
.query-long {
width: 250px !important;
margin-right: 10px;
}
.query-tree {
width: 300px !important;
margin-right: 10px;
}
.query-longest {
width: 350px !important;
margin-right: 10px;
}
.query-400 {
width: 400px !important;
margin-right: 10px;
}
.query-450 {
width: 450px !important;
margin-right: 10px;
}
.query-500 {
width: 500px !important;
margin-right: 10px;
}
.auto-height {
height: 78vh;
overflow-x: hidden;
overflow-y: scroll;
}

24
vue/src/assets/style/conf.scss

@ -0,0 +1,24 @@ @@ -0,0 +1,24 @@
// 定义一些全局变量样式
/* 文字相关 */
$text-color-grey: rgba($color: #ffffff, $alpha: .7);
/* 边框 */
$border-color: #414A7A;
$border: 1px solid $border-color;
/* 高亮背景色 */
$active-background-color: #1D3B8A;
$active-background-color1: #3A7BFF;
$highlight-color: #8493E3;
/* 主要颜色 */
$main-color: #18295E;
$sec-color: #3E4583;
$email-background-color: rgba(0, 24, 106, 0.3);
$success-color: #67C23A;
$warning-color: #E6A23C;
$danger-color: #F56C6C;
// @import '_common.scss';

13
vue/src/assets/style/dark.scss

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
.style-dark .el-table *, .style-dark .el-tabs *,
.style-dark .el-input__inner,
.style-dark h1, .style-dark h2, .style-dark small, .style-dark span, .style-dark div *{
color: white;
}
.style-dark .el-input__inner * {
background: none !important;
}
.style-dark .el-dialog *{
color: white !important;
}

197
vue/src/assets/style/element.scss

@ -0,0 +1,197 @@ @@ -0,0 +1,197 @@
@import 'conf.scss';
.el-input__wrapper {
padding: 0px !important;
margin-right: 10px;
}
.el-input__inner {
padding: 0px 15px !important;
}
.el-input__suffix {
padding-right: 15px !important;
}
.el-input__prefix {
padding-left: 15px !important;
}
.el-input__inner,
.el-input__wrapper {
background: transparent !important;
color: rgba(17, 17, 17, 0.747) !important;
}
.el-dialog {
background-color: transparent !important;
// background-image: url('~@/assets/img/background.png') !important;
background-size: cover !important;
background-position: center !important;
background-repeat: no-repeat !important;
.el-dialog__title {
color: rgba(17, 17, 17, 0.747);
}
}
/* 针对table组件的修改 */
.my-table {
.el-table {
font-size: 12px;
background-color: transparent;
color: rgba(17, 17, 17, 0.747);
thead {
color: rgba(17, 17, 17, 0.747);
.cell {
text-align: center;
}
}
td,
th {
padding: 6px 0;
}
th,
tr {
background-color: transparent;
}
th.is-leaf {
// border-bottom: $border;
}
td {
// border-bottom: $border;
}
&::before {
// background-color: $border-color;
}
.el-table__body tr.current-row > td {
background: $active-background-color;
}
}
.el-table--enable-row-hover .el-table__body tr:hover > td {
background: rgba(226, 226, 226, 0.705);
}
.el-table .el-table__body tr.current-row > td {
background: rgba(226, 226, 226, 0.705);
}
.el-table__expand-icon {
color: rgba(17, 17, 17, 0.747);
}
.el-table__body tr.hover-row > td {
background: rgba(226, 226, 226, 0.705);
}
}
.el-table--striped .el-table__body tr.el-table__row--striped td {
background: rgba($color: #9b9eb8, $alpha: 0.2);
}
/* 针对表单组件的修改 */
.my-form {
// display: inline-block;
color: rgba(17, 17, 17, 0.747);
.el-input__inner,
.el-textarea__inner {
background: transparent;
// border-color: $border-color;
color: rgba(17, 17, 17, 0.747);
}
.el-form-item__label {
color: rgba(17, 17, 17, 0.747);
}
.el-checkbox {
color: rgba(17, 17, 17, 0.747);
margin-right: 15px !important;
}
}
/* 时间选取组件 */
.my-date-picker {
display: inline-block;
.el-input__inner {
// border: $border;
.el-range-input {
background-color: transparent !important;
}
}
.el-range-input {
background: $main-color;
color: rgba(17, 17, 17, 0.747);
}
.el-range-separator,
.el-input__inner,
.el-range__icon {
color: rgba(17, 17, 17, 0.747);
}
}
/* 针对tab组件修改 */
.my-tabs {
.el-tabs__item {
color: rgba(17, 17, 17, 0.747);
}
.el-tabs__nav-wrap::after {
height: 0px;
}
}
// /* 分页 */
// .el-pagination {
// .el-pagination__total {
// color: rgba(17, 17, 17, 0.747);
// }
// }
.my-pagination {
color: rgba(17, 17, 17, 0.747);
.el-input__inner,
.el-textarea__inner {
background: transparent;
// border-color: $border-color;
color: rgba(17, 17, 17, 0.747);
}
.el-form-item__label {
color: rgba(17, 17, 17, 0.747);
}
.el-pagination {
.btn-next {
background-color: transparent !important;
.el-icon-arrow-right {
color: #50a3f7;
&:hover {
color: rgba(17, 17, 17, 0.747);
}
}
}
.btn-prev {
background-color: transparent !important;
.el-icon-arrow-left {
color: #50a3f7;
&:hover {
color: rgba(17, 17, 17, 0.747);
}
}
}
.el-pager {
li {
background: transparent;
}
.number {
color: #50a3f7 !important;
}
.is-active {
color: rgba(17, 17, 17, 0.747) !important;
}
.el-icon-more {
color: #50a3f7 !important;
}
.el-icon-d-arrow-left {
border-radius: 10px;
}
.el-icon-d-arrow-right {
border-radius: 10px;
}
}
.el-pagination__jump {
color: rgba(17, 17, 17, 0.747);
}
}
}

0
vue/src/components/5gs/profile/index.vue

33
vue/src/components/5gs/profile/slice.vue

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
<template>
<el-row>
<el-button v-if="slice.length < 8" type="success" icon="el-icon-plus" size="small" class="form-item-btn"
@click="add">FLOW
</el-button>
</el-row>
</template>
<script lang="ts" setup>
import { useStore } from "vuex";
import { get, post, put, delate } from "@/config/axios";
import { reactive, onMounted, ref, defineProps } from "vue";
const store = useStore();
const prop = defineProps({
slice: {
type: Array,
default: []
},
prefix: {
type: String,
defaul: null
}
});
const add = () => {
prop.slice.push({});
}
onMounted(() => { });
</script>
<style lang="scss" scoped>
//@import url(); css
</style>

15
vue/src/components/main/aside.vue

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
<template>
</template>
<script lang="ts" setup>
import { useStore } from "vuex";
import { get, post, put, delate } from "@/config/axios";
import { reactive, onMounted, ref } from "vue";
const { state, commit, dispatch } = useStore();
onMounted(() => {});
</script>
<style lang="scss" scoped>
//@import url(); css
</style>

15
vue/src/components/main/header.vue

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
<template>
</template>
<script lang="ts" setup>
import { useStore } from "vuex";
import { get, post, put, delate } from "@/config/axios";
import { reactive, onMounted, ref } from "vue";
const { state, commit, dispatch } = useStore();
onMounted(() => {});
</script>
<style lang="scss" scoped>
//@import url(); css
</style>

5
vue/src/components/template/index.ts

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
import ListTable from './listTable.vue';
import ModifyForm from './modifyForm.vue';
import SearchRow from './searchRow.vue';
export { ListTable, ModifyForm, SearchRow };

76
vue/src/components/template/listTable.vue

@ -0,0 +1,76 @@ @@ -0,0 +1,76 @@
<template>
<div class="my-table">
<el-table :data="page ? data.data : data"
:height="height ? height : (page ? state.sizes.pTableHei : state.sizes.tableHei)" highlight-current-row>
<el-table-column v-for="item in props" :key="item.code" :prop="item.code" :label="item.name"
:min-width="item.width" :width="item.type ? item.width : ''" :align="item.align ? item.align : 'center'"
show-overflow-tooltip :formatter="formatter">
<template v-if="item.slot" #default="scope">
<slot :name="item.code" :row="scope.row"></slot>
</template>
</el-table-column>
<el-table-column v-if="prop.actionWidth" label="操作" :min-width="prop.actionWidth" align="center">
<template #default="scope">
<slot v-bind:row="scope.row" v-bind:$index="scope.$index"></slot>
</template>
</el-table-column>
</el-table>
</div>
<div v-if="page" class="my-pagination">
<el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange"
:current-page="example.page" :page-sizes="[10, 20, 50, 100, 200]" :page-size="example.size"
layout="total, sizes, prev, pager, next, jumper" :total="data.total">
</el-pagination>
</div>
</template>
<script lang="ts" setup>
import { useStore } from "vuex";
import { get, post, put, delate } from "@/config/axios";
import { reactive, onMounted, ref, defineEmits, defineProps } from "vue";
import { formatter } from "@/plugs/formatter";
const prop = defineProps({
data: {
type: Object,
default: null
},
props: {
type: Array<any>(),
default: [],
},
page: {
type: Boolean,
default: false,
},
actionWidth: {
type: Number,
default: null,
},
height: {
type: Number,
default: null,
},
example: {
type: Object,
default: null,
},
});
const { state } = useStore();
const emit = defineEmits(["query"]);
const handleSizeChange = (val) => {
prop.example.size = val;
emit("query");
};
const handleCurrentChange = (val) => {
prop.example.page = val;
emit("query");
};
onMounted(() => { });
</script>
<style lang="scss" scoped>
//@import url(); css
</style>

69
vue/src/components/template/modifyForm.vue

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
<template>
<div class="modify-form">
<el-form label-position="right" :class="class" :label-width="width ? width + 'px' : ''" :model="param"
ref="modifyForm" :rules="rules">
<slot></slot>
<el-form-item class="form-btns">
<el-button type="primary" @click="formConfirm">确定</el-button>
<el-button type="primary" @click="emit('cancel')">取消</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script lang="ts" setup>
import { useStore } from "vuex";
import { get, post, put, delate } from "@/config/axios";
import { reactive, onMounted, ref, watch } from "vue";
import { FormInstance } from "element-plus";
const store = useStore();
const prop = defineProps({
width: {
type: Number,
default: 80,
},
param: {
type: Object,
default: {},
},
rules: {
type: Object,
default: {},
},
class: {
type: String,
default: '',
},
});
const emit = defineEmits(["confirm", "cancel"]);
const modifyForm = ref<FormInstance>();
const formConfirm = () => {
if (!modifyForm.value) return;
modifyForm.value?.validate((valid, fields) => {
if (valid) {
emit("confirm");
}
});
};
const clearValidate = () => {
modifyForm.value?.clearValidate();
}
watch(() => prop.param, () => {
modifyForm.value?.clearValidate();
});
onMounted(() => {
});
</script>
<style lang="scss">
//@import url(); css
.modify-form {
.el-select {
width: 100%;
}
}
</style>

70
vue/src/components/template/searchRow.vue

@ -0,0 +1,70 @@ @@ -0,0 +1,70 @@
<template>
<div class="search-row">
<div class="left">
<span class="title" v-if="title">{{ title }}</span>
<el-button v-if="fresh" type="info" icon="Refresh" @click="emit('query')"> </el-button>
<el-button v-if="add" type="primary" icon="Plus" @click="emit('add')"> 添加 </el-button>
<el-button v-if="del" type="danger" icon="Delete" @click="emit('delete')"> 删除 </el-button>
<slot name="left"></slot>
</div>
<div class="query">
<slot></slot>
<el-button v-if="query" type="primary" icon="Search" @click="emit('query')"> 查询 </el-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { useStore } from "vuex";
import { get, post, put, delate } from "@/config/axios";
import { reactive, onMounted, ref } from "vue";
import { defineProps, defineEmits } from "vue";
// import { Refresh, Plus, Delete, Search } from "@element-plus/icons-vue";
const store = useStore();
const prop = defineProps({
title: {
type: String,
default: null
},
fresh: {
type: Boolean,
default: true,
},
add: {
type: Boolean,
default: true,
},
del: {
type: Boolean,
default: true,
},
query: {
type: Boolean,
default: true,
},
});
const emit = defineEmits(["query", "add", "delete"]);
onMounted(() => { });
</script>
<style lang="scss" scoped>
//@import url(); css
.query {
text-align: right;
float: right;
}
.left {
float: left;
}
.title {
font-weight: bold;
padding-right: 15px;
font-size: 20px;
min-width: 20px;
}
</style>

29
vue/src/config/apiUrl.ts

@ -0,0 +1,29 @@ @@ -0,0 +1,29 @@
export const publik = {
background: '/background.jpg',
webConfig: '/webConfig.json',
login: '/public/login',
logout: '/public/logout',
info: '/public/info',
updatePassword: 'public/password',
};
export const user = {
root: '/sys/user',
lock: '/sys//user/lock',
reset: '/sys//user/reset',
clean: '/sys//user/data',
};
export const log = {
sys: '/sys/log',
service: '/sys//log/service',
export: '/sys//log/service/export',
};
export const cube = {
root: '/cube',
init: '/cube/init',
assemble: '/cube/assemble',
select: '/cube/select',
dimension: '/cube/dimension'
};

257
vue/src/config/axios.ts

@ -0,0 +1,257 @@ @@ -0,0 +1,257 @@
/* eslint-disable */
import axios from 'axios';
import store from '@/store';
import { loading, close, showMessage } from './element';
const config = {
baseURL: process.env.VUE_APP_BASE_URL ? '/api' : '',
timeout: 60 * 1000,
withCredentials: true, // Check cross-site Access-Control
};
const _axios = axios.create(config);
_axios.defaults.headers.post['Content-Type'] = 'application/json;charset=UTF-8';
_axios.defaults.headers.put['Content-Type'] = 'application/json;charset=UTF-8';
_axios.defaults.headers.delete['Content-Type'] =
'application/json;charset=UTF-8';
let curMsg: any;
// Add a request interceptor
_axios.interceptors.request.use(
function (config) {
const time = new Date().getTime().toString();
const params = config.params;
if (params) {
delEmpty(params);
params.t = time;
}
const data = config.data;
if (data != null && typeof data === 'object') {
delEmpty(data);
data.t = time;
}
return config;
},
function (error) {
return Promise.reject(error);
}
);
function delEmpty(data) {
if (data) {
for (const item in data) {
if (data.hasOwnProperty(item)) {
const val = data[item];
if ((val == null || val == 'null' || val == '') && val !== 0) {
delete data[item];
}
}
}
}
}
// Add a response interceptor
_axios.interceptors.response.use(
function (response) {
return response.data;
},
function (error) {
return Promise.reject(error);
}
);
export function post(url, data?, noMsg?, noLoading?) {
return new Promise((resolve, reject) => {
if (!noLoading) {
loading();
}
_axios.post(url, data).then(
(response: any) => {
handResponse(response, resolve, noMsg, noLoading);
},
(err) => {
handError(err, reject, noMsg, noLoading);
}
);
});
}
export function get(url, data?, noMsg?, noLoading?) {
return new Promise((resolve, reject) => {
if (!noLoading) {
loading();
}
_axios.get(url, { params: data }).then(
(response: any) => {
handResponse(response, resolve, noMsg, noLoading);
},
(err) => {
handError(err, reject, noMsg, noLoading);
}
);
});
}
export function put(url, data?, noMsg?, noLoading?) {
return new Promise((resolve, reject) => {
if (!noLoading) {
loading();
}
_axios.put(url, data).then(
(response: any) => {
handResponse(response, resolve, noMsg, noLoading);
},
(err) => {
handError(err, reject, noMsg, noLoading);
}
);
});
}
export function delate(url, data?, noMsg?, noLoading?) {
return new Promise((resolve, reject) => {
if (!noLoading) {
loading();
}
// _axios.delete(url, data).then(
// (response: any) => {
// handResponse(response, resolve, noMsg, noLoading);
// },
// (err) => {
// handError(err, reject, noMsg, noLoading);
// }
// );
_axios({
method: 'delete',
url: url,
data: data,
}).then(
(response: any) => {
handResponse(response, resolve, noMsg, noLoading);
},
(err) => {
handError(err, reject, noMsg, noLoading);
}
);
});
}
function handResponse(response, resolve, noMsg, noLoading) {
if (!noLoading) {
close();
}
if (response.success) {
if (response.message) {
if (!noMsg) {
showMessage('success', response.message, false);
}
resolve(true);
} else if (response.data) {
resolve(response.data);
} else {
resolve(true);
}
} else {
if (response.message == 'session timeout') {
store.dispatch('logout');
}
if (response.message == 'no permission') {
response.message = '您暂无权访问,请联系管理员添加';
}
if (response.message?.indexOf('no permission for ') == 0) {
response.message = '此权限尚未开放,请勿越权访问';
}
if (!noMsg) {
showMessage('error', response.message, false);
}
response.error && console.log(response.error);
resolve(false);
}
}
function handError(err, reject, noMsg, noLoading) {
if (!noLoading) {
close();
}
if (!noMsg) {
showMessage('error', '网络错误', false);
}
reject(err);
}
export function upload(file, url, data) {
return new Promise((resolve, reject) => {
let param = new FormData(); // 创建form对象
param.append('file', file); // 通过append向form对象添加数据
if (data) {
for (const item in data) {
if (data.hasOwnProperty(item)) {
param.append(item, data[item]); // 添加form表单中其他数据
}
}
}
let config = {
headers: { 'Content-Type': 'multipart/form-data' },
};
_axios.post(url, param, config).then(
(response) => {
handResponse(response, resolve, false, false);
},
(err) => {
handError(err, reject, false, false);
}
);
});
}
export function download(fileName, url, data = {}, callBack?) {
loading();
_axios({
method: 'get',
url: url, // 请求地址
params: data, // 参数
responseType: 'blob', // 表明返回服务器返回的数据类型
}).then(
(response: any) => {
close();
const reader = new FileReader() as any;
reader.readAsText(response);
reader.onload = function () {
try {
const result = JSON.parse(reader.result);
if (typeof result === 'object') {
showMessage('error', result.message, false);
if (callBack != null) {
callBack(false);
}
return;
}
} catch (err) {}
const blob = new Blob([response], {
type: 'application/vnd.ms-excel',
});
const navigator = window.navigator as any;
if (navigator.msSaveOrOpenBlob) {
navigator.msSaveBlob(blob, fileName);
} else {
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = fileName;
link.click();
window.URL.revokeObjectURL(link.href);
}
if (callBack != null) {
callBack(true);
}
};
},
(err) => {
close();
showMessage('error', '下载失败,网络异常!', false);
if (callBack != null) {
callBack(false);
}
}
);
}

88
vue/src/config/element.ts

@ -0,0 +1,88 @@ @@ -0,0 +1,88 @@
import {
ElLoading,
ElMessage,
ElNotification,
ElMessageBox,
Action,
} from 'element-plus';
let curMsg;
let count = 0;
let instance;
export function loading() {
if (count === 0) {
instance = ElLoading.service({
lock: true,
text: '加载中...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.3)',
});
}
count++;
}
export function close() {
if (count <= 0) {
return;
}
if (--count === 0) {
instance.close();
}
}
export function showMessage(type, info, unClose) {
if (curMsg != null) {
curMsg.close();
}
const tmp = ElMessage({
type: type,
showClose: true,
message: info,
duration: 3000,
offset: 50,
});
if (!unClose) {
curMsg = tmp;
}
}
export function showNotify(type, title, message, position) {
if (position == null) {
position = 'top-right';
}
ElNotification({
title: title,
type: type,
message: message,
position: position,
duration: 0,
});
}
export function warning(msg) {
showMessage('warning', msg, false);
}
export function error(msg) {
showMessage('error', msg, false);
}
export function success(msg) {
showMessage('success', msg, false);
}
export const confirm = (msg: string, title: string) => {
return new Promise((resolve, reject) => {
ElMessageBox.confirm(msg, title, {
confirmButtonText: '确定',
cancelButtonText: '取消',
buttonSize: 'default',
confirmButtonClass:'el-button--info',
type: 'warning',
})
.then(resolve)
.catch(() => {
//do nothing
});
});
};

90
vue/src/config/menu.ts

@ -0,0 +1,90 @@ @@ -0,0 +1,90 @@
export const menu = [
{
user: {
name: '账号管理',
active: true,
},
log: {
name: '日志管理',
active: false,
},
},
{
user: {
name: '账号管理',
active: true,
},
case: {
name: '案件管理',
active: false,
},
log: {
name: '日志管理',
active: false,
},
},
{
target: {
name: '目标管理',
active: true,
},
reconnCode: {
name: '侦码动态',
active: false,
unread: 0,
},
reconnDynamic: {
name: '侦听动态',
active: false,
},
reconnListen: {
name: '侦听详情',
active: false,
unread: 0,
},
analysis: {
name: '人群分析',
active: false,
},
flux: {
name: '流量管理',
active: false,
},
history: {
name: '历史记录',
active: false,
},
device: {
name: '设备管理',
active: true,
},
log: {
name: '日志管理',
active: false,
},
},
{
profile: {
name: '开户模版',
active: true,
},
Subscriber: {
name: '开户',
active: false,
unread: 0,
},
},
{
cube: {
name: '数据立方',
active: true,
},
},
];
export const homes = [
{ path: 'user', show: true },
{ path: 'user', show: true },
{ path: 'task', show: false },
{ path: 'profile', show: true },
{ path: 'cube', show: false },
];

18
vue/src/main.ts

@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import zhCn from 'element-plus/es/locale/lang/zh-cn';
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const app = createApp(App);
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component);
}
app.use(ElementPlus, { size: 'default', locale: zhCn });
app.use(store).use(router).mount('#app');

31
vue/src/plugs/dict/5gs.ts

@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
export const units = {
0: 'bps',
1: 'Kbps',
2: 'Mbps',
3: 'Gbps',
4: 'Tbps',
};
export const context = {
1: '主叫',
2: '被叫',
3: '主动',
};
export const extens = {
'_X.': '_X.',
};
export const appData = {
Dial: 'PJSIP/${EXTEN},60',
Macro: 'DIAL,${EXTEN}',
};
export const activeStatus = {
A: '已启用',
I: '已禁用',
};
export const smsType = {
R: '被叫',
S: '主叫',
};
export const smsAction = {
Y: '拦截',
N: '放行',
};

11
vue/src/plugs/dict/base.ts

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
export const cube_status = {
0: '未初始化',
1: '空闲',
2: '执行构建中',
3: '构建失败',
};
export const dimension_dataType = {
0: '字符',
1: '数字',
2: '时间',
};

94
vue/src/plugs/dict/device.ts

@ -0,0 +1,94 @@ @@ -0,0 +1,94 @@
export const device_Status = {
0: '自适应中',
1: '工作状态',
2: '告警',
3: '故障',
4: '启动中',
};
export const device_cardTotalStatus = {
0: '初始状态',
1: '切换角色中',
2: '手动重启',
3: '禁用状态',
4: '异常',
5: '在线',
};
export const device_cardCurrentStatus = {
0: '离线',
1: '小区未建立',
2: '小区建立中',
3: '小区建立成功',
4: '切换角色失败',
5: '查询配置失败',
6: '工作模式',
7: '功放查询配置失败',
};
export const device_syncMode = {
0: '宏站同步',
1: 'GPS同步',
4: '自适应',
5: '无同步模式',
};
export const powerMeasure = {
46: '40W',
42: '16W',
40: '10W',
37: '5W',
33: '2W',
30: '1W',
27: '0.5W',
24: '0.25W',
23: '0.2W',
20: '0.1W',
17: '0.05W',
10: '10MW',
0: '1MW',
};
const arr1 = [24, 42];
const arr2 = [24, 46];
const arr3 = [24, 33];
const arr4 = [0, 23];
export const powerRange = {
0: arr1,
1: arr1,
2: arr1,
3: arr1,
4: arr1,
5: arr2,
6: arr2,
7: arr3,
8: arr4,
};
export const device_interdict = {
1: '上网',
2: '短信',
4: 'volte通话',
8: '上行短信',
16: '下行短信',
};
export const device_soldierNum = {
'-1': '侦模块',
1: '听模块',
};
export const device_powerSwitch = {
0: '关',
1: '开',
};
export const auto_conf_model = {
1: '普通机型',
2: '小米12',
};
export const standards = {
0: 'GSM',
1: 'WCDMA',
};
export const tac_cycle = {
0: '不自动更新',
1: '1分钟',
2: '2分钟',
5: '5分钟',
10: '10分钟',
30: '30分钟',
};

6
vue/src/plugs/dict/index.ts

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
export * from './device';
// import * as device from './device';
// import * as base from './base';
// export * from "./user";
export * from "./5gs";
export * from './base';

45
vue/src/plugs/formatter.ts

@ -0,0 +1,45 @@ @@ -0,0 +1,45 @@
import * as dict from './dict';
export const getValue = (
row: any,
column: string,
value: any,
index?: number
) => {
if ((value == null || value == '') && value !== 0) {
return '';
}
return value;
};
export const formatter = (row: any, column: any, value: any, index: number) => {
return formatterByDist(row.scheme + '_' + column.property, value);
};
export const formatterByDist = (dictKey, value) => {
if (!dictKey) {
return getValue(null, '', value);
}
const mapping = dict[dictKey];
if (mapping == null) {
return getValue(null, '', value);
}
return mapping[value] == null ? value : mapping[value];
};
export const formartLink = (row) => {
const up = row.ambr.uplink;
const down = row.ambr.downlink;
return (
down.value + dict.units[down.unit] + '/' + up.value + dict.units[up.unit]
);
};
export const formartApn = (row, index) => {
const session = row.slice[0].session[index];
if (session) {
return session.name + '-' + session.qos.index;
} else {
return '';
}
};

119
vue/src/plugs/validate/device.ts

@ -0,0 +1,119 @@ @@ -0,0 +1,119 @@
import { useStore } from 'vuex';
import { warning } from '@/config/element';
const store = useStore();
export const fddFreqs = [
[
//B38/41
[37750, 38249],
[39650, 41589],
],
[
//B39/34
[38250, 38649],
[36200, 36349],
],
[
//B40
[38650, 39649],
],
];
export function repeat(arr) {
arr = JSON.parse(JSON.stringify(arr));
arr.sort();
for (let i = 1; i < arr.length; i++) {
if (arr[i - 1] == arr[i]) {
return true;
}
}
return false;
}
export function getIndex(freq) {
freq = parseInt(freq);
for (let i = 0; i < fddFreqs.length; i++) {
for (let j = 0; j < fddFreqs[i].length; j++) {
const arr = fddFreqs[i][j];
if (arr[0] <= freq && freq <= arr[1]) {
return i;
}
}
}
return -1;
}
export function getUsedFreqs(device) {
const arr: number[] = [];
store.state.devCode.forEach((item) => {
if (item.deviceId != device.deviceId && item.sModType == 'TDD') {
const freqs = [item.devFreq].concat(item.defaultFreq.split(','));
for (let i = 0; i < freqs.length; i++) {
const freq = freqs[i];
const index = getIndex(freq);
if (index > -1) {
arr.push(index);
}
}
}
});
return arr;
}
export function enable(device, freq) {
let enable = false;
freq = parseInt(freq);
const freqs = device.uarfcnband.split(',');
for (let i = 0; i < freqs.length; i++) {
const arr = freqs[i].split('-');
if (freq >= parseInt(arr[0]) && freq <= parseInt(arr[1])) {
enable = true;
break;
}
}
return enable;
}
export function checkFreq(device, freq) {
if (!enable(device, freq)) {
warning('配置的频点不在可配范围内,请确认后重新配置');
return false;
}
if (device.sModType != 'TDD') {
return true;
}
const arr = getUsedFreqs(device);
const index = getIndex(freq);
if (index > -1 && arr.indexOf(index) > -1) {
warning('频点不能配置在其他TDD模块已配置频段内');
return false;
}
return true;
}
export function checkFreqs(device, freqs) {
if (repeat(freqs)) {
warning('轮询频点不可重复,请确认后重新配置');
return false;
}
const arr = getUsedFreqs(device);
for (let i = 0; i < freqs.length; i++) {
if (!enable(device, freqs[i])) {
warning('第' + (i + 1) + '个轮询频点不在可配范围内,请确认后重新配置');
return false;
}
const index = getIndex(freqs[i]);
if (index > -1 && arr.indexOf(index) > -1) {
warning('第' + (i + 1) + '个轮询频点不能配置在其他TDD模块已配置频段内');
return false;
}
}
return true;
}
export function checkCycle(device) {
if (device.freqSwitchCycle < 10 || device.freqSwitchCycle > 120) {
warning('轮询周期必须为10-120之间的整数!');
return false;
}
return true;
}

2
vue/src/plugs/validate/main.ts

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
export * from "./device";
export * from "./user";

11
vue/src/plugs/validate/user.ts

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
export const validatePass = (rule: any, value: any, callback: any) => {
if (value === '') {
callback(new Error('请输入密码'));
} else {
const pwtest = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[$@$!%*?&_])[A-Za-z\d$@$!%*?&_]{6,}$/;
if (pwtest.test(value) == false) {
callback(new Error('密码格式有误'));
}
callback();
}
};

26
vue/src/router/index.ts

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import Login from '../views/main/login.vue';
const routes: Array<RouteRecordRaw> = [
{
path: '/',
redirect: '/cube',
},
{
path: '/login',
name: 'login',
component: () => import('../views/main/login.vue')
},
{
path: '/cube',
name: 'user',
component: () => import('../views/admin/cube.vue')
}
];
const router = createRouter({
history: createWebHashHistory(),
routes,
});
export default router;

6
vue/src/shims-vue.d.ts vendored

@ -0,0 +1,6 @@ @@ -0,0 +1,6 @@
/* eslint-disable */
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}

19
vue/src/store/actions.ts

@ -0,0 +1,19 @@ @@ -0,0 +1,19 @@
import axios from 'axios';
import { publik } from '@/config/apiUrl';
import router from '@/router';
import {post, get, put, delate} from '@/config/axios';
export default {
getWebConfig({ commit }) {
axios.get(publik.webConfig).then((rsp) => {
commit('updateState', { prop: 'webConf', value: rsp.data });
document.title = rsp.data.web_title;
});
},
logout({ commit }) {
put(publik.logout).then((rsp) => {
commit('updateState', { prop: 'user', value: {} });
router.push('/');
});
},
};

12
vue/src/store/index.ts

@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
import { createStore } from 'vuex';
import state from './state';
import mutations from './mutations';
import actions from './actions';
export default createStore({
state,
getters: {},
mutations,
actions,
modules: {},
});

47
vue/src/store/mutations.ts

@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
import { menu, homes } from '@/config/menu';
import router from "@/router";
export default {
updateState(state, param) {
state[param.prop] = param.value;
},
initSizes(state) {
const height = window.innerHeight;
const width = window.innerWidth;
state.widescreen = width > 1800;
const loginHeight = 350;
const loginWidth = 450;
const navHead = state.showHeader ? 60 : 0;
const aside = state.showAside ? 250 : 0;
const map = {
medium: 50,
small: 42,
mini: 35,
};
const head = 40;
const pd = 15;
const page = 37;
state.sizes.height = height;
state.sizes.width = width;
state.sizes.fullHeight = height + 'px';
state.sizes.loginHeight = loginHeight + 'px';
state.sizes.loginWidth = loginWidth + 'px';
state.sizes.loginTop = (height - loginHeight) * 0.4 + 'px';
state.sizes.loginLeft = (width - loginWidth) * 0.49 + 'px';
state.sizes.headHeight = navHead + 'px';
state.sizes.asideWidth = aside + 'px';
state.sizes.mainHead = head + 'px';
state.sizes.mainHeight = height - navHead + 'px';
state.sizes.tableHei = height - navHead - head - pd;
state.sizes.pTableHei = state.sizes.tableHei - page;
},
setUserInfo(state, user) {
state.user = user;
const home = homes[user.type];
state.showHeader = home.show;
state.showAside = home.show;
router.push(home.path);
state.curPath = home.path;
state.menu = JSON.parse(JSON.stringify(menu[user.type]));
},
};

15
vue/src/store/state.ts

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
export default {
sizes: {},
webConf: {},
showHeader: false,
showAside: false,
widescreen: true,
size: 'large',
user: {},
menu: [],
curPath: '',
unread: {
msg: 0,
call: 0,
},
};

139
vue/src/views/admin/cube.vue

@ -0,0 +1,139 @@ @@ -0,0 +1,139 @@
<template>
<div id="cube">
<SearchRow :example="example" @query="queryCube" :query="false" :del="false" title="数据立方"
@add="param = {}; flag.add = true;"></SearchRow>
<ListTable :data="list" :props="props" :actionWidth="220" :height="state.sizes.height - 90">
<template #default="{ row: { name, status } }">
<el-button type="primary" @click="showDetail(name)">管理</el-button>
<el-button v-if="status > 0" type="primary" @click="select(name)">查询</el-button>
<el-button type="danger" @click="delCube(name)">删除</el-button>
</template>
</ListTable>
</div>
<el-dialog :title="selectTitle" v-model="flag.select" :close-on-click-modal="false" width="95%" top="2%">
<CubeData :cube="cube"></CubeData>
</el-dialog>
<el-dialog title="新增立方体" v-model="flag.add" width="50%" :close-on-click-modal="false">
<ModifyForm :param="param" :rules="rules" @confirm="addCube" @cancel="flag.add = false" :width="100">
<el-form-item label="立方体表名" prop="name">
<el-input type="text" v-model="param.name" auto-complete="off" :maxlength="100"></el-input>
</el-form-item>
<el-form-item label="数据源表名" prop="source">
<el-input type="text" v-model="param.source" auto-complete="off" :maxlength="100"></el-input>
</el-form-item>
<el-form-item label="源表时间字段" prop="sourceTimer">
<el-input type="text" v-model="param.sourceTimer" auto-complete="off" :maxlength="64"></el-input>
</el-form-item>
<el-form-item label="备注(名称)" prop="remark">
<el-input type="text" v-model="param.remark" auto-complete="off" :maxlength="64"></el-input>
</el-form-item>
</ModifyForm>
</el-dialog>
<el-dialog title="立方体管理" v-model="flag.detail" width="98%" top="1%" :close-on-click-modal="false">
<CubeDetail :name="cubeName" @fresh="select(cube.name)"></CubeDetail>
</el-dialog>
</template>
<script lang="ts" setup>
import { useStore } from "vuex";
import { get, post, put, delate } from "@/config/axios";
import { reactive, onMounted, ref } from "vue";
import { cube as urls } from "@/config/apiUrl";
import { ListTable, SearchRow, ModifyForm } from "@/components/template";
import { confirm } from "@/config/element";
import CubeData from "./cubeData.vue";
import CubeDetail from "./cubeDetail.vue"
const { state, commit, dispatch } = useStore();
const example = reactive<any>({
})
const list = ref<any>([]);
const flag = reactive({
select: false,
add: false,
detail: false
});
const selectTitle = ref<string>("");
const cube = ref<any>({});
const cubeName = ref<any>({});
const param = ref<any>({});
const rules = reactive({
})
const props = [
{ code: 'name', name: '立方体表名', width: '180' },
{ code: 'source', name: '数据源表', width: '180' },
{ code: 'remark', name: '备注', width: '150' },
{ code: 'status', name: '状态', width: '100' },
{ code: 'total', name: '数据量', width: '120' },
{ code: 'createTime', name: '创建时间', width: '170' },
{ code: 'assembleTime', name: '最近构建成功时间', width: '170' },
]
const queryCube = () => {
get(urls.root, example).then(rsp => {
if (rsp) list.value = rsp;
})
}
const addCube = () => {
post(urls.root, param.value).then(rsp => {
rsp && queryCube;
})
}
const showDetail = name => {
cubeName.value = name;
flag.detail = true;
}
const select = name => {
if (name == cube.value.name) {
flag.select = true;
return;
}
get(urls.root + '/' + name).then((rsp: any) => {
if (rsp) {
rsp.title = '立方体:' + rsp.name;
if (rsp.remark) rsp.title += ('(' + rsp.remark + ')');
rsp.title += ' 数据查询';
cube.value = rsp;
cube.value.page = 1;
cube.value.size = 20;
flag.select = true;
}
})
}
const delCube = name => {
confirm('确认要删除立方体' + name + '吗?', '提示').then(() => {
delate(urls.root, name).then(rsp => {
rsp && queryCube()
})
});
}
onMounted(() => {
queryCube();
});
</script>
<style lang="scss" scoped>
//@import url(); css
#cube {
padding: 20px;
}
.cell-item {
display: flex;
align-items: center;
}
.cube-item {
margin: 20px 50px;
}
</style>

67
vue/src/views/admin/cubeData.vue

@ -0,0 +1,67 @@ @@ -0,0 +1,67 @@
<template>
<SearchRow :del="false" :add="false" :fresh="false" title="选择维度:" @query="selectData">
<template v-slot:left>
<el-checkbox v-for="item in cube.dimensions" size="default" v-model="item.checked" :label="item.remark" />
</template>
<template #default>
<el-input v-for="item in cube.dimensions" v-show="item.checked" v-model="item.value" class="query-medium"
clearable :placeholder="'输入' + item.remark" />
</template>
</SearchRow>
<ListTable :example="cube" :data="data" :props="props" :height="state.sizes.height - 290" page @query="selectData">
</ListTable>
</template>
<script lang="ts" setup>
import { useStore } from "vuex";
import { get, post, put, delate } from "@/config/axios";
import { cube as urls } from "@/config/apiUrl";
import { reactive, onMounted, ref, watch } from "vue";
import { ListTable, SearchRow } from "@/components/template";
const { state, commit, dispatch } = useStore();
const prop = defineProps({
cube: {
type: Object,
default: {},
}
});
const data = ref<any>({
total: 0,
data: []
});
const props = ref<any>([]);
const selectData = () => {
init(prop.cube);
put(urls.select, prop.cube).then(rsp => {
data.value = rsp ? rsp : [];
})
}
const init = (cube) => {
props.value = [];
if (cube && cube.dimensions) {
cube.dimensions.forEach(i => {
if (i.checked)
props.value.push({ code: i.column, name: i.remark, width: 150 });
});
}
if (cube && cube.data) {
cube.data.forEach(i => {
props.value.push({ code: i.column, name: i.remark, width: 150 });
});
}
}
// watch(() => prop.cube, (n, o) => {
// init(n);
// });
onMounted(() => {
init(prop.cube);
});
</script>
<style lang="scss" scoped>
//@import url(); css
</style>

150
vue/src/views/admin/cubeDetail.vue

@ -0,0 +1,150 @@ @@ -0,0 +1,150 @@
<template>
<el-descriptions class="margin-top" title="" :column="3" size="large" border>
<template #title>
<el-button type="warning" @click="initCube(name)">初始化</el-button>
<el-button :disabled="cube.status == 0" type="primary" @click="assemble(name)">构建</el-button>
<el-button type="primary" icon="Plus" @click="showAdd(0)"> 添加维度 </el-button>
<el-button type="primary" icon="Plus" @click="showAdd(1)"> 添加统计 </el-button>
</template>
<el-descriptions-item label="立方体表名">{{ cube.name }}</el-descriptions-item>
<el-descriptions-item label="数据源表名">{{ cube.source }}</el-descriptions-item>
<el-descriptions-item label="数据源时间字段">{{ cube.sourceTimer }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ cube.createTime }}</el-descriptions-item>
<el-descriptions-item label="状态">{{ formatterByDist('cube_status', cube.status) }}</el-descriptions-item>
<el-descriptions-item label="数据量">{{ cube.total }}</el-descriptions-item>
<el-descriptions-item label="最近一次构建成功时间">{{ cube.assembleTime }}</el-descriptions-item>
</el-descriptions> <br>
<h2>维度信息</h2>
<ListTable :data="cube.dimensions" :props="props" :actionWidth="220" :height="state.sizes.height - 600">
<template #default="{ row }">
<!-- <el-button type="primary" @click="modify(row)">修改</el-button> -->
<el-button type="danger" @click="delDimension(row)">删除</el-button>
</template>
</ListTable>
<h2>统计信息</h2>
<ListTable :data="cube.data" :props="props" :actionWidth="220" :height="150">
<template #default="{ row }">
<!-- <el-button type="primary" @click="modify(row)">修改</el-button> -->
<el-button type="danger" @click="delDimension(row)">删除</el-button>
</template>
</ListTable>
<el-dialog title="新增维度" v-model="flag.add" width="50%" :close-on-click-modal="false">
<ModifyForm :param="dimension" :rules="rules" @confirm="addDimension" @cancel="flag.add = false" :width="100">
<el-form-item label="维度表字段" prop="column">
<el-input type="text" v-model="dimension.column" auto-complete="off" :maxlength="64"></el-input>
</el-form-item>
<el-form-item label="维度数据源" prop="dimesion">
<el-input type="text" v-model="dimension.dimesion" auto-complete="off" :maxlength="255"></el-input>
</el-form-item>
<el-form-item label="备注(名称)" prop="remark">
<el-input type="text" v-model="dimension.remark" auto-complete="off" :maxlength="64"></el-input>
</el-form-item>
<el-form-item label="字段数据类型" prop="dataType">
<el-select v-model="dimension.dataType">
<el-option v-for="(val, key, i) in dimension_dataType" :label="val" :value="Number(key)"
:key="key"></el-option>
</el-select>
</el-form-item>
<el-form-item v-if="dimension.dataType == 0" label="字段长度" prop="length">
<el-input type="number" v-model="dimension.length" auto-complete="off" :max="255"></el-input>
</el-form-item>
</ModifyForm>
</el-dialog>
</template>
<script lang="ts" setup>
import { useStore } from "vuex";
import { cube as urls } from "@/config/apiUrl";
import { get, post, put, delate } from "@/config/axios";
import { reactive, onMounted, ref, watch } from "vue";
import { confirm } from "@/config/element";
import { formatterByDist } from "@/plugs/formatter";
import { dimension_dataType } from "@/plugs/dict";
import { ListTable, ModifyForm } from "@/components/template";
const { state, commit, dispatch } = useStore();
const prop = defineProps({
name: {
type: String,
default: null,
}
});
const cube = ref<any>({});
const dimension = ref<any>({});
const flag = reactive({
add: false
})
const rules = [
]
const props = [
{ code: "column", name: '维度表字段', width: 150 },
{ code: "dimesion", name: '维度数据源', width: 150 },
{ code: "remark", name: '备注', width: 150 },
{ code: "dataType", name: '数据类型', width: 120 },
{ code: "length", name: '字段长度', width: 100 },
]
const getCube = () => {
get(urls.root + '/' + prop.name).then((rsp: any) => {
if (rsp) {
cube.value = rsp;
}
})
}
const showAdd = type => {
dimension.value = { cubeName: cube.value.name, type: type };
flag.add = true;
}
const addDimension = () => {
post(urls.dimension, dimension.value).then(rsp => {
if (rsp) {
getCube();
flag.add = false;
}
})
}
const modify = row => { }
const delDimension = row => {
confirm('初始化后的立方体删除维度后需重新初始化,是否确认删除?', '提示').then(() => {
delate(urls.dimension, row).then(rsp => {
rsp && getCube()
})
});
}
const initCube = name => {
confirm('初始化操作将清空立方体数据,请确认是否继续?', '提示').then(() => {
put(urls.init, name).then(rsp => {
if (rsp) {
getCube();
}
})
});
}
const assemble = name => {
confirm('是否确认提交构建任务?', '提示').then(() => {
post(urls.assemble, name).then(rsp => {
rsp && getCube()
})
});
}
watch(() => prop.name, (n, o) => {
getCube();
})
onMounted(() => {
getCube();
});
</script>
<style lang="scss" scoped>
//@import url(); css
</style>

129
vue/src/views/main/login.vue

@ -0,0 +1,129 @@ @@ -0,0 +1,129 @@
<template>
<el-row class="content">
<el-col :span="state.widescreen ? 4 : 6" :offset="state.widescreen ? 10 : 9">
<el-form :model="user" :rules="rules" ref="loginform" label-width="0px" :style="{
'height': state.sizes.loginHeight,
'margin-top': state.sizes.loginTop
}" size="large">
<!-- <img v-if="$store.state.webConf.login_form_logo_show" class="logo" src="../../assets/img/logo.png"> -->
<el-form-item>
<div class="lte-title"> {{ state.webConf.web_title }} </div>
</el-form-item>
<el-form-item prop="userId">
<el-input v-model="user.userId" placeholder="用户名"></el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="user.password" type="password" placeholder="密码"></el-input>
</el-form-item>
<el-form-item>
<el-button @click="login(loginform)"> </el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</template>
<script lang="ts" setup>
import { onBeforeMount, reactive, ref } from "vue";
import { useStore } from "vuex";
import type { FormInstance } from "element-plus";
import md5 from "js-md5";
import { post } from "@/config/axios";
import { publik } from "@/config/apiUrl";
const { state, commit } = useStore();
const loginform = ref<FormInstance>();
const user = reactive({
userId: "",
password: "",
});
const rules = reactive({
userId: [
{ required: true, message: "请输入用户名", trigger: "blur" },
{ min: 3, max: 8, message: "请输入3-8位的用户名", trigger: "blur" },
],
password: [
{ required: true, message: "请输入密码", trigger: "change" },
{ min: 6, max: 16, message: "请输入6-16位的密码", trigger: "blur" },
],
});
const homes = ["user", "case", "task"];
onBeforeMount(() => {
commit("updateState", { prop: "showHeader", value: false });
commit("updateState", { prop: "showAside", value: false });
commit("initSizes");
});
const login = async (formEl: FormInstance | undefined) => {
if (!formEl) return;
await formEl.validate((valid, fields) => {
if (valid) {
const param = JSON.parse(JSON.stringify(user));
param.password = md5(param.password);
post(publik.login, param).then((response: any) => {
if (response) {
commit("setUserInfo", response);
commit("initSizes");
// router.push(homes[response.type]);
}
});
}
});
};
</script>
<style lang='scss' scoped>
//@import url(); css
.el-row.content {
padding: 16px;
z-index: 2;
}
.lte-title {
font-size: 1.5em;
font-weight: bold;
text-align: center;
width: 100%;
color: rgb(49, 89, 143);
}
.el-input {
margin: 12px 0;
}
.el-button {
width: 100%;
}
.el-row {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
}
.el-col {
border-radius: 4px;
}
#home {
position: relative;
width: 100%;
height: 100%;
overflow-x: hidden;
background: rgba(255, 255, 255, 0);
}
.canvas {
position: fixed;
z-index: -1; // background-color: black;
}
.logo {
width: 300px;
height: 90px;
}
</style>

42
vue/tsconfig.json

@ -0,0 +1,42 @@ @@ -0,0 +1,42 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": true,
"jsx": "preserve",
"moduleResolution": "node",
"experimentalDecorators": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"useDefineForClassFields": true,
"noImplicitAny": false,
"sourceMap": true,
"baseUrl": ".",
"types": [
"webpack-env"
],
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
},
"include": [
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"
]
}

17
vue/vue.config.js

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
const { defineConfig } = require('@vue/cli-service');
module.exports = defineConfig({
transpileDependencies: true,
devServer: {
proxy: {
'/api': {
target: process.env.VUE_APP_BASE_URL,
// 允许跨域
changeOrigin: true,
ws: true,
pathRewrite: {
'^/api': '',
},
},
},
},
});

5867
vue/yarn.lock

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save