Browse Source

初版提交

包含登录、图片管理和配置管理功能
master
许孟阳 3 years ago
parent
commit
5dc49f4994
  1. 33
      java/.classpath
  2. 1
      java/.gitignore
  3. 23
      java/.project
  4. 4
      java/.settings/org.eclipse.core.resources.prefs
  5. 7
      java/.settings/org.eclipse.jdt.core.prefs
  6. 4
      java/.settings/org.eclipse.m2e.core.prefs
  7. 14
      java/application.properties
  8. 23
      java/log4j2.xml
  9. 78
      java/pom.xml
  10. 17
      java/src/main/java/vip/xumy/picture/PictureRescueApplocation.java
  11. 38
      java/src/main/java/vip/xumy/picture/conf/PictureApplicationRunner.java
  12. 20
      java/src/main/java/vip/xumy/picture/conf/PictureRescueInitializer.java
  13. 69
      java/src/main/java/vip/xumy/picture/ctrl/PictureController.java
  14. 25
      java/src/main/java/vip/xumy/picture/ctrl/PublicControoler.java
  15. 42
      java/src/main/java/vip/xumy/picture/mapper/PictureMapper.java
  16. 25
      java/src/main/java/vip/xumy/picture/pojo/Picture.java
  17. 115
      java/src/main/java/vip/xumy/picture/service/PictureService.java
  18. 5
      vue/.editorconfig
  19. 2
      vue/.env.development
  20. 2
      vue/.env.preview
  21. 2
      vue/.env.production
  22. 2
      vue/.env.test
  23. 22
      vue/.gitignore
  24. 24
      vue/README.md
  25. 5
      vue/babel.config.js
  26. 1
      vue/debug.log
  27. 14222
      vue/package-lock.json
  28. 70
      vue/package.json
  29. BIN
      vue/public/favicon.ico
  30. 18
      vue/public/index.html
  31. BIN
      vue/src/assets/logo.png
  32. 23
      vue/src/assets/styles/back_blue.scss
  33. 12
      vue/src/assets/styles/back_grey.scss
  34. 166
      vue/src/assets/styles/base.scss
  35. 4
      vue/src/assets/styles/size_medium.scss
  36. 4
      vue/src/assets/styles/size_mini.scss
  37. 4
      vue/src/assets/styles/size_small.scss
  38. 182
      vue/src/components/main/NavHeader.vue
  39. 72
      vue/src/components/template/mainHeader.vue
  40. 267
      vue/src/components/template/manage.vue
  41. 62
      vue/src/components/template/modifyItem.vue
  42. 17
      vue/src/main.ts
  43. 37
      vue/src/router/index.ts
  44. 13
      vue/src/shims-tsx.d.ts
  45. 4
      vue/src/shims-vue.d.ts
  46. 289
      vue/src/static/plugins/axios.ts
  47. 47
      vue/src/static/plugins/element.ts
  48. 56
      vue/src/static/plugins/websocket.ts
  49. 139
      vue/src/static/tool/BMapUtil.js
  50. 55
      vue/src/static/tool/dateUtil.js
  51. 80
      vue/src/static/tool/ruleControl.js
  52. 233
      vue/src/static/tool/validate.js
  53. 13
      vue/src/store/actions.js
  54. 15
      vue/src/store/index.ts
  55. 3
      vue/src/store/modules.js
  56. 175
      vue/src/store/mutations.js
  57. 11
      vue/src/store/state.js
  58. 243
      vue/src/views/biz/picture.vue
  59. 66
      vue/src/views/main/App.vue
  60. 124
      vue/src/views/main/login.vue
  61. 88
      vue/src/views/sys/config.vue
  62. 39
      vue/tsconfig.json
  63. 39
      vue/vue.config.js
  64. 9363
      vue/yarn.lock

33
java/.classpath

@ -0,0 +1,33 @@ @@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

1
java/.gitignore vendored

@ -0,0 +1 @@ @@ -0,0 +1 @@
/target/

23
java/.project

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>picture-bed</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

4
java/.settings/org.eclipse.core.resources.prefs

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
eclipse.preferences.version=1
encoding//src/main/java=UTF-8
encoding//src/main/resources=UTF-8
encoding/<project>=UTF-8

7
java/.settings/org.eclipse.jdt.core.prefs

@ -0,0 +1,7 @@ @@ -0,0 +1,7 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.methodParameters=generate
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=1.8

4
java/.settings/org.eclipse.m2e.core.prefs

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
activeProfiles=
eclipse.preferences.version=1
resolveWorkspaceProjects=true
version=1

14
java/application.properties

@ -0,0 +1,14 @@ @@ -0,0 +1,14 @@
server.port=80
server.servlet.context-path=/
version.num=1.0.0
# 程序自身数据源配置
spring.datasource.url=jdbc:mysql://localhost:3306/picture?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=51131420
picture.root.path=E:/image
timeout.login.token=1000000
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=100MB

23
java/log4j2.xml

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Configuration后面的status,这个用于设置log4j2自身内部的信息输出 -->
<!-- monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数。 -->
<configuration status="error" monitorInterval="60">
<appenders>
<!--控制台 -->
<Console name="Console" target="SYSTEM_OUT">
<!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch) -->
<ThresholdFilter level="debug" onMatch="ACCEPT"
onMismatch="DENY" />
<!--输出日志的格式 -->
<PatternLayout
pattern="%d{HH:mm:ss.SSS} [%p] %class{36} %L %M - %msg%xEx%n" />
</Console>
</appenders>
<loggers>
<logger name="vip.xumy" level="DEBUG"></logger>
<root level="warn">
<appender-ref ref="Console" />
</root>
</loggers>
</configuration>

78
java/pom.xml

@ -0,0 +1,78 @@ @@ -0,0 +1,78 @@
<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.0.2.RELEASE</version>
</parent>
<groupId>vip.xumy.picture</groupId>
<artifactId>picture-bed</artifactId>
<version>1.0.0</version>
<name>picture-bed</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<xumy.admin.version>1.2.0</xumy.admin.version>
<log4j.version>1.2.17</log4j.version>
<fastjson.version>1.2.46</fastjson.version>
<mybatis-spring-boot.version>1.3.2</mybatis-spring-boot.version>
<druid.version>1.1.9</druid.version>
<mybatis.pagehelper.version>1.2.5</mybatis.pagehelper.version>
<sqlitejdbc.version>0.5.6</sqlitejdbc.version>
<bouncycastle.version>1.46</bouncycastle.version>
</properties>
<dependencies>
<dependency>
<groupId>vip.xumy.admin</groupId>
<artifactId>xumy_admin</artifactId>
<version>${xumy.admin.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>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- SpringBoot 的测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- Package as an executable jar -->
<build>
<finalName>pciture</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<executable>true</executable>
</configuration>
</plugin>
</plugins>
</build>
</project>

17
java/src/main/java/vip/xumy/picture/PictureRescueApplocation.java

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
package vip.xumy.picture;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import vip.xumy.admin.sys.conf.SysInitializer;
import vip.xumy.admin.verify.conf.VerifyInitializer;
import vip.xumy.picture.conf.PictureRescueInitializer;
public class PictureRescueApplocation extends SpringBootServletInitializer {
public static void main(String[] args) {
Class<?>[] arr = new Class<?>[] { VerifyInitializer.class, SysInitializer.class, PictureRescueInitializer.class };
SpringApplication.run(arr, args);
}
}

38
java/src/main/java/vip/xumy/picture/conf/PictureApplicationRunner.java

@ -0,0 +1,38 @@ @@ -0,0 +1,38 @@
package vip.xumy.picture.conf;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import vip.xumy.admin.sys.service.ConfigService;
/**
* @author:mengyxu
* @date:2020年3月25日
*/
@Component
@Configuration
public class PictureApplicationRunner implements ApplicationRunner {
private static final String HOSTR_CFG_KEY = "intranet_host";
private static final String HOSTE_CFG_KEY = "internet_host";
public static Map<String, String> HOST_MAP = new HashMap<>();;
@Autowired
public void setConfigService(ConfigService configService) {
HOST_MAP.put("traHost", configService.getStringConfig(HOSTR_CFG_KEY, null));
HOST_MAP.put("tertHost", configService.getStringConfig(HOSTE_CFG_KEY, null));
}
@Override
public void run(ApplicationArguments args) throws Exception {
// TODO Auto-generated method stub
}
}

20
java/src/main/java/vip/xumy/picture/conf/PictureRescueInitializer.java

@ -0,0 +1,20 @@ @@ -0,0 +1,20 @@
package vip.xumy.picture.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.picture")
@MapperScan("vip.xumy.picture.**.mapper")
public class PictureRescueInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
return builder.sources(PictureRescueInitializer.class);
}
}

69
java/src/main/java/vip/xumy/picture/ctrl/PictureController.java

@ -0,0 +1,69 @@ @@ -0,0 +1,69 @@
package vip.xumy.picture.ctrl;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
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.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 org.springframework.web.multipart.MultipartFile;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import vip.xumy.admin.utils.LoginUtil;
import vip.xumy.core.exception.CoreException;
import vip.xumy.core.pojo.com.AjaxResponse;
import vip.xumy.core.pojo.com.PageResponse;
import vip.xumy.picture.pojo.Picture;
import vip.xumy.picture.service.PictureService;
/**
* Do not use for any commercial purposes without permission
*
* @author: mengyxu
* @date: 2021年12月31日
*/
@RestController
@RequestMapping("picture")
public class PictureController {
@Autowired
private PictureService pictureService;
@GetMapping
public PageResponse<Picture> list(Picture example, HttpServletRequest request) {
example.setUser(LoginUtil.getUserId(request));
Page<Picture> pages = PageHelper.startPage(example.getPage(), example.getSize());
List<Picture> list = pictureService.list(example);
PageResponse<Picture> rsp = new PageResponse<>();
rsp.setRows(list);
rsp.setTotal(pages.getTotal());
return rsp;
}
@PostMapping
public AjaxResponse save(MultipartFile file, HttpServletRequest request) throws CoreException {
pictureService.save(file, LoginUtil.getUserId(request));
return new AjaxResponse(true, "上传成功");
}
@PutMapping
public AjaxResponse update(@RequestBody Picture picture) throws CoreException {
pictureService.update(picture);
return new AjaxResponse(true, "更新成功");
}
@DeleteMapping
public AjaxResponse delete(@RequestBody Picture picture) throws CoreException {
pictureService.delete(picture);
return new AjaxResponse(true, "删除成功");
}
}

25
java/src/main/java/vip/xumy/picture/ctrl/PublicControoler.java

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
package vip.xumy.picture.ctrl;
import java.util.Map;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import vip.xumy.picture.conf.PictureApplicationRunner;
/** Do not use for any commercial purposes without permission
* @author: mengyxu
* @date: 2021年12月31日
*/
@RestController
@RequestMapping("public")
public class PublicControoler {
@GetMapping("host")
public Map<String, String> getHost(){
return PictureApplicationRunner.HOST_MAP;
}
}

42
java/src/main/java/vip/xumy/picture/mapper/PictureMapper.java

@ -0,0 +1,42 @@ @@ -0,0 +1,42 @@
package vip.xumy.picture.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.Select;
import org.apache.ibatis.annotations.Update;
import vip.xumy.picture.pojo.Picture;
/**
* Do not use for any commercial purposes without permission
*
* @author: mengyxu
* @date: 2021年12月31日
*/
@Mapper
public interface PictureMapper {
@Select({ "<script>", "SELECT * FROM user_picture ", "<where>", "<if test='user != null'> AND user = #{user} </if>",
"<if test='startTime != null'> AND time >= #{startTime} </if>",
"<if test='endTime != null'> AND #{endTime} > time </if>",
"<if test='remark != null'> AND remark LIKE CONCAT('%', #{remark}, '%') </if>",
"<if test='status != null'> AND status = #{status} </if>", "</where>", "ORDER BY time DESC", "</script>" })
List<Picture> list(Picture example);
@Insert({ "INSERT INTO user_picture VALUES (#{id}, #{user}, NOW(), #{path}, #{space}, #{status}, #{remark})" })
void save(Picture picture);
@Update({ "UPDATE user_picture SET status = #{status}, remark = #{remark} WHERE id = #{id}" })
void update(Picture picture);
@Delete({ "DELETE FROM user_picture WHERE id = #{id}" })
void delete(String id);
@Delete({ "DELETE FROM user_picture WHERE user = #{user}" })
void deleteAll(String user);
}

25
java/src/main/java/vip/xumy/picture/pojo/Picture.java

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
package vip.xumy.picture.pojo;
import lombok.Getter;
import lombok.Setter;
import vip.xumy.core.pojo.base.BasePeriod;
/**
* Do not use for any commercial purposes without permission
*
* @author: mengyxu
* @date: 2021年12月31日
*/
@Setter
@Getter
public class Picture extends BasePeriod {
private String id;
private String user;
private String path;
private String space;
private String status;
private String remark;
}

115
java/src/main/java/vip/xumy/picture/service/PictureService.java

@ -0,0 +1,115 @@ @@ -0,0 +1,115 @@
package vip.xumy.picture.service;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.UUID;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import vip.xumy.core.exception.CoreException;
import vip.xumy.core.utils.ImageType;
import vip.xumy.picture.mapper.PictureMapper;
import vip.xumy.picture.pojo.Picture;
/**
* Do not use for any commercial purposes without permission
*
* @author: mengyxu
* @date: 2021年12月31日
*/
@Service
public class PictureService {
@Autowired
private PictureMapper pictureMapper;
@Value("${picture.root.path}")
public void setRootPath(String path) {
if (path != null && path.endsWith("/")) {
this.rootPath = path;
} else {
this.rootPath = path + "/";
}
}
private String rootPath;
public List<Picture> list(Picture example) {
return pictureMapper.list(example);
}
public Picture save(MultipartFile file, String user) throws CoreException {
byte[] data;
try {
data = file.getBytes();
} catch (IOException e) {
throw new CoreException("图片上传失败");
}
ImageType type = ImageType.getImageType(data);
if (type == null) {
throw new CoreException("未知的图片类型");
}
String uuid = UUID.randomUUID().toString();
String path = uuid.replace("-", "/") + "." + type.getType();
writeToFile(path, data);
Picture picture = new Picture();
picture.setUser(user);
picture.setId(uuid.replace("-", ""));
picture.setPath(path);
picture.setStatus("0");
picture.setSpace(Math.ceil(data.length * 1.0 / 1024) + "KB");
pictureMapper.save(picture);
return picture;
}
private void writeToFile(String path, byte[] data) throws CoreException {
File file = new File(rootPath + path);
File parent = file.getParentFile();
if (!parent.exists()) {
file.mkdirs();
}
try (FileOutputStream fos = new FileOutputStream(file)) {
fos.write(data);
} catch (IOException e) {
e.printStackTrace();
file.delete();
deleteEmptyDir(parent);
throw new CoreException("图片保存失败");
}
}
public void update(Picture picture) {
pictureMapper.update(picture);
}
public void delete(Picture picture) {
deleteFile(picture.getPath());
pictureMapper.delete(picture.getId());
}
private void deleteFile(String path) {
File file = new File(rootPath + path);
file.deleteOnExit();
deleteEmptyDir(file.getParentFile());
}
private void deleteEmptyDir(File dir) {
if (!dir.exists() || !dir.isDirectory()) {
return;
}
if (dir.list().length == 0) {
dir.delete();
deleteEmptyDir(dir.getParentFile());
}
}
public void deleteAll(String user) {
pictureMapper.deleteAll(user);
}
}

5
vue/.editorconfig

@ -0,0 +1,5 @@ @@ -0,0 +1,5 @@
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true

2
vue/.env.development

@ -0,0 +1,2 @@ @@ -0,0 +1,2 @@
NODE_ENV="development"
VUE_APP_BASE_URL="localhost"

2
vue/.env.preview

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

2
vue/.env.production

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

2
vue/.env.test

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

22
vue/.gitignore vendored

@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
.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 @@
# monitor-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'
]
}

1
vue/debug.log

@ -0,0 +1 @@ @@ -0,0 +1 @@
[1105/164547.236:ERROR:directory_reader_win.cc(43)] FindFirstFile: 系统找不到指定的路径。 (0x3)

14222
vue/package-lock.json generated

File diff suppressed because it is too large Load Diff

70
vue/package.json

@ -0,0 +1,70 @@ @@ -0,0 +1,70 @@
{
"name": "picture",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "vue-cli-service serve",
"build": "vue-cli-service build --mode production",
"lint": "vue-cli-service lint",
"preview": "vue-cli-service build --mode preview",
"test": "vue-cli-service build --mode test"
},
"dependencies": {
"core-js": "^3.6.5",
"vue": "^2.6.11",
"vue-class-component": "^7.2.3",
"vue-property-decorator": "^8.4.2",
"vue-router": "^3.2.0",
"vuex": "^3.4.0"
},
"devDependencies": {
"@babel/core": "^7.10.5",
"@typescript-eslint/eslint-plugin": "^2.33.0",
"@typescript-eslint/parser": "^2.33.0",
"@vue/cli-plugin-babel": "^4.4.0",
"@vue/cli-plugin-eslint": "^4.4.0",
"@vue/cli-plugin-router": "^4.4.6",
"@vue/cli-plugin-typescript": "^4.4.0",
"@vue/cli-plugin-vuex": "^4.4.6",
"@vue/cli-service": "^4.4.0",
"@vue/eslint-config-standard": "^5.1.2",
"@vue/eslint-config-typescript": "^5.0.2",
"axios": "^0.19.2",
"babel-loader": "^8.1.0",
"cache-loader": "^4.1.0",
"element-ui": "^2.15.6",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.2.2",
"js-md5": "^0.7.3",
"node-sass": "^4.14.1",
"sass-loader": "^9.0.2",
"typescript": "~3.9.3",
"vue-cli-plugin-axios": "^0.0.4",
"vue-cli-plugin-element": "^1.0.1",
"vue-clipboard2": "^0.3.1",
"vue-template-compiler": "^2.6.11"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/recommended",
"@vue/standard",
"@vue/typescript/recommended"
],
"parserOptions": {
"ecmaVersion": 2020
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

BIN
vue/public/favicon.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

18
vue/public/index.html

@ -0,0 +1,18 @@ @@ -0,0 +1,18 @@
<!DOCTYPE html>
<html lang="en">
<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 style="margin: 0px;">
<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>

BIN
vue/src/assets/logo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

23
vue/src/assets/styles/back_blue.scss

@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
@charset "UTF-8";
body{
background-color: cadetblue;
}
.app-head{
background-color: #00ffff;
}
.app-main{
// background-color: #5500ff;
}
.main-table{
background-color: #409EFF;
}
.el-empty__description p{
color: black !important;
}
.el-table__empty-text{
color: black !important;
}

12
vue/src/assets/styles/back_grey.scss

@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
@charset "UTF-8";
.app-head{
background-color: #E4E7ED;
}
.app-main{
background-color: #EBEEF5;
}
.main-table{
background-color: #F2F6FC;
}

166
vue/src/assets/styles/base.scss

@ -0,0 +1,166 @@ @@ -0,0 +1,166 @@
@charset "UTF-8";
* {
margin: 0;
padding: 0;
}
html,
body {
font-size: 14px;
font-family: "Microsoft YaHei";
width: 100%;
height: 100%;
overflow: hidden;
}
.el-header, .main-head, .main-table{
padding: 0;
}
// 公共样式文件
// 带阴影的表格外的div
div.table-warpper {
margin: 0 2.5%;
padding: 0 0.5%;
border: 1px solid #20A0FF;
overflow: hidden;
}
.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;
}
.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;
}
.modify-dialog .el-dialog {
background-color: #edf2f7;
}
.form-title {
font-size: 18px;
background-color: cadetblue;
padding: 10px;
height: 25px;
font-weight: bold;
margin-bottom: 20px;
color: #FFFFFF;
}
.full-item-form .el-select,
.full-item-form .el-date-editor,
.full-item-form .el-autocomplete {
width: 100% !important;
}
.dialog-footer {
text-align: right;
}
.infomation-form .el-form-item {
margin-top: 20px;
font-size: 1.5rem;
}
.infomation-form .el-input {
width: 350px;
margin-right: 20px;
}
.infomation-form .el-input__inner {
background-color: #4472c4 !important;
color: #FFFFFF;
}
// 表格背景透明, 便于定制整体背景样式
.main-table .el-table,
.el-table__expanded-cell {
background-color: transparent;
border: none;
}
.main-table .el-table tr {
background-color: transparent !important;
border: none;
}
.main-table .el-table--enable-row-transition .el-table__header td,
.el-table .cell {
background-color: transparent;
}
.main-table .el-table--enable-row-transition .el-table__body td,
.el-table .cell {
background-color: transparent;
}

4
vue/src/assets/styles/size_medium.scss

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
@charset "UTF-8";
.main-table .el-table td, .main-table .el-table th{
padding: 9px 0px;
}

4
vue/src/assets/styles/size_mini.scss

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
@charset "UTF-8";
.main-table .el-table td, .main-table .el-table th{
padding: 3px 0px;
}

4
vue/src/assets/styles/size_small.scss

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
@charset "UTF-8";
.main-table .el-table td, .main-table .el-table th{
padding: 5px 0px;
}

182
vue/src/components/main/NavHeader.vue

@ -0,0 +1,182 @@ @@ -0,0 +1,182 @@
<template>
<div id="header">
<el-row>
<el-col :span="12">
<el-row class="menu-row">
<el-menu class="el-menu-demo" :default-active="activeGroup" mode="horizontal" text-color="#000"
active-text-color="#4669e7">
<el-menu-item v-for="item in $store.state.menuGroups" :key="item.id" :index="item.id"
@click="clickGroup(item)">
{{ item.name }}
</el-menu-item>
</el-menu>
<el-radio-group :size="$store.state.size" v-model="active">
<el-radio-button v-for="item in menus" :label="item.id">
{{ item.name }}
</el-radio-button>
</el-radio-group>
</el-row>
</el-col>
<el-col :span="12">
<el-row class="info" type="flex" justify="end" align="middle">
<el-button class="info-btn" icon="el-icon-user-solid" type="text">
{{ $store.state.userInfo.userId }}
</el-button>
<el-button :size="$store.state.size" icon="el-icon-setting" type="info" @click="update = true">
修改密码
</el-button>
<el-button :size="$store.state.size" icon="el-icon-switch-button" type="info"
@click="$store.commit('logout')">
注销登录
</el-button>
</el-row>
</el-col>
</el-row>
<el-dialog title="修改密码" :visible.sync="update" width="40%" top="15vh" :close-on-click-modal="false"
@keydown.enter.native="confirm()">
<el-form ref="update" :model="user" :rules="rules" label-width="180px">
<el-form-item label="旧密码" prop="password">
<el-input type="password" v-model="user.password"></el-input>
</el-form-item>
<el-form-item label="新密码" prop="newPwd">
<el-input type="password" v-model="user.newPwd"></el-input>
</el-form-item>
<el-form-item label="重复新密码" prop="rePwd">
<el-input type="password" v-model="user.rePwd" autocomplete="off"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="update = false;"> </el-button>
<el-button type="primary" @click="confrim"> </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
Vue,
Component,
Prop,
Watch
} from 'vue-property-decorator';
import md5 from 'js-md5';
import {
password
} from '@/static/tool/validate.js';
@Component
export default class NavHeader extends Vue {
menus = [];
active = 'home';
activeGroup = 'home';
update = false;
user = {};
rules = {
password: [{
required: true,
message: '请输入密码',
trigger: 'blur'
}],
newPwd: [{
validator: password,
trigger: 'blur'
}],
rePwd: [{
validator: this.reKey,
trigger: 'blur'
}]
};
clickGroup(group) {
this.activeGroup = group.id
this.menus = group.child
const child = group.child
if (child.length == 0) {
this.active = group.id
} else {
this.active = child[0].id
}
}
clickMenu(id) {
this.active = id
}
toHome() {
this.clickGroup(this.$store.state.menuGroups[0]);
}
confrim() {
const that = this
this.$refs.update.validate((valid) => {
if (valid) {
const param = JSON.parse(JSON.stringify(that.user));
param.userId = that.$store.state.userInfo.userId;
param.password = md5(param.password)
param.newPwd = md5(param.newPwd)
delete param.rePwd;
that.$post('public/update/password', param).then(rsp => {
if (rsp) {
that.update = false;
that.user = {};
that.$store.commit('logout')
}
});
} else {
return false
}
});
}
reKey(rule, value, callback) {
if (!value) {
callback(new Error('请重复输入新密码'));
} else if (value != this.user.newPwd) {
callback(new Error('两次输入的新密码不一致'));
} else {
callback();
}
}
@Watch('active')
activeChang(n, o) {
this.$router.push('/' + n)
}
created() {}
mounted() {} // - 访DOM
beforeCreate() {} // -
beforeMount() {} // -
beforeUpdate() {} // -
updated() {} // -
beforeDestroy() {} // -
destroyed() {} // -
activated() {} // keep-alive
}
</script>
<style scoped>
.info {
/* background-color: cadetblue; */
height: 80px;
padding-right: 100px;
}
.el-menu.el-menu--horizontal {
border-bottom: none;
}
.info-btn {
font-size: 17px;
color: #2D3748;
margin-left: 30px !important;
}
.menu-row {
background-color: #FFFFFF;
padding-left: 30px;
}
</style>

72
vue/src/components/template/mainHeader.vue

@ -0,0 +1,72 @@ @@ -0,0 +1,72 @@
<template>
<div id="mainHeader" class="main-head" :style="{ height: $store.state.sizes.mainHead }">
<el-row>
<el-col :span="8" v-if="flag.title">
<slot name="title"></slot>
</el-col>
<el-col :span="flag.title ? 16 : 24">
<el-row type="flex" justify="end">
<span v-for="term in terms">
<el-date-picker :size="$store.state.size" v-if="term.datePicker" v-model="example[term.code]"
:value-format="term.valueFormat" :type="term.type" :class="term.claze ? term.claze : 'query-medium'"
:placeholder="term.desc"></el-date-picker>
<el-select :size="$store.state.size" v-if="term.dict" v-model="example[term.code]"
:class="term.claze ? term.claze : 'query-medium'" :placeholder="term.desc" :filterable="term.filterable"
clearable>
<el-option v-for="(val, key, i) in $store.state.dict[term.dict]" :key="key" :value="key" :label="val" />
</el-select>
<el-input :size="$store.state.size" v-if="!term.datePicker && !term.dict" v-model="example[term.code]"
:class="term.claze ? term.claze : 'query-medium'" :placeholder="term.desc" clearable />
</span>
<el-button :size="$store.state.size" type="primary" icon="el-icon-search" @click="$emit('query')"
v-if="flag.list"> 查询
</el-button>
<el-button :size="$store.state.size" type="success" icon="el-icon-plus" @click="add()" v-if="flag.save">
添加{{name}}
</el-button>
<slot name="header"></slot>
</el-row>
</el-col>
</el-row>
</div>
</template>
<script>
import {
Vue,
Component,
Prop
} from "vue-property-decorator";
@Component
export default class MainHeader extends Vue {
@Prop({
type: String
}) name;
@Prop({
type: Object
}) flag;
@Prop({
type: Array
}) terms;
@Prop({
type: Object
}) example;
created() {} // - 访this
mounted() {} // - 访DOM
beforeCreate() {} // -
beforeMount() {} // -
beforeUpdate() {} // -
updated() {} // -
beforeDestroy() {} // -
destroyed() {} // -
activated() {} //keep-alive
}
</script>
<style scoped>
</style>

267
vue/src/components/template/manage.vue

@ -0,0 +1,267 @@ @@ -0,0 +1,267 @@
<template>
<div id="mamageTemp">
<MainHead :name="name" :flag="flag" :terms="terms" :example="example" @query="query(1)"></MainHead>
<!-- <el-row class="main-head" :style="{ height: $store.state.sizes.mainHead }">
<el-row type="flex" justify="end">
<span v-for="term in terms">
<el-date-picker :size="$store.state.size" v-if="term.datePicker" v-model="example[term.code]"
:value-format="term.valueFormat" :type="term.type" :class="term.claze ? term.claze : 'query-medium'"
:placeholder="term.desc"></el-date-picker>
<el-select :size="$store.state.size" v-if="term.dict" v-model="example[term.code]"
:class="term.claze ? term.claze : 'query-medium'" :placeholder="term.desc" :filterable="term.filterable"
clearable>
<el-option v-for="(val, key, i) in $store.state.dict[term.dict]" :key="key" :value="key" :label="val" />
</el-select>
<el-input :size="$store.state.size" v-if="!term.datePicker && !term.dict" v-model="example[term.code]"
:class="term.claze ? term.claze : 'query-medium'" :placeholder="term.desc" clearable />
</span>
<el-button :size="$store.state.size" type="primary" icon="el-icon-search" @click="query(1)" v-if="flag.list"> 查询
</el-button>
<el-button :size="$store.state.size" type="success" icon="el-icon-plus" @click="add()" v-if="flag.save">
添加{{name}}
</el-button>
<slot name="header"></slot>
</el-row>
</el-row> -->
<el-row class="main-table">
<el-table :data="result.rows" :height="tableHeight ? tableHeight : $store.state.sizes.pTableHei" border>
<el-table-column v-for="prop in props" v-if="!prop.tableHide" :prop="prop.code" :label="prop.name"
:min-width="prop.width" :width="prop.type ? prop.width : ''" :type="prop.type"
:formatter="(row,column,value) => getValue(prop.dict,value)" show-overflow-tooltip>
</el-table-column>
<el-table-column v-if="flag.update || flag.del || actions" label="操作" :width="actionWidth" fixed="right">
<template slot-scope="scope" v-if="!stateProp || scope.row[stateProp] != 'R'">
<el-button v-if="flag.update" size="mini" type="primary" @click="modify(scope.row)"> 修改 </el-button>
<el-button v-if="flag.del" size="mini" type="danger" @click="delate(scope.row)"> 删除 </el-button>
<el-button v-for="action in actions" size="mini" :type="action.type" @click="$emit(action.emit,scope.row)">
{{action.name}}
</el-button>
</template>
</el-table-column>
</el-table>
<el-pagination :current-page="example.page" :page-sizes="[10, 20, 50, 100]" :page-size="example.size"
layout="total, sizes, prev, pager, next, jumper" :total="result.total" @size-change="handleSizeChange"
@current-change="handleCurrentChange" />
</el-row>
<el-dialog append-to-body :title="(flag.add ? '添加':'修改') + name" :visible.sync="flag.modify"
:close-on-click-modal="false" top="15vh" :width="props.length > 8 ? '60%' : '40%'"
@keydown.enter.native="confirm()">
<el-form ref="update" class="full-item-form" label-width="180px" :rules="rules" :model="param">
<el-row v-if="props.length > 8">
<el-col :span="12" v-for="prop in props">
<ModifyItem :prop="prop" :flag="flag" :param="param"></ModifyItem>
</el-col>
</el-row>
<el-row v-else>
<span v-for="prop in props">
<ModifyItem :prop="prop" :flag="flag" :param="param"></ModifyItem>
</span>
</el-row>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="flag.modify = false"> 取消 </el-button>
<el-button type="primary" @click="confirm"> 确认 </el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import {
Vue,
Component,
Prop,
Watch
} from 'vue-property-decorator';
import ModifyItem from './modifyItem.vue';
import MainHead from './mainHeader.vue';
@Component({
components: {
ModifyItem,
MainHead
}
})
export default class MamageTemp extends Vue {
@Prop({
type: String
}) name;
@Prop({
type: String
}) url;
@Prop({
type: Object,
}) flag;
@Prop({
type: Array
}) terms;
@Prop({
type: Array
}) props;
@Prop({
type: Array
}) actions;
@Prop({
type: Object
}) rules;
@Prop({
type: String
}) stateProp;
@Prop({
type: String
}) size;
@Prop({
type: Number
}) tableHeight;
actionWidth = 30;
example = {
page: 1,
size: 20
};
result = {
rows: [],
total: 0
};
param = {};
handleSizeChange(val) {
this.example.size = val
this.query()
}
handleCurrentChange(val) {
this.example.page = val
this.query()
}
query(page) {
const that = this
if (page != null) {
this.example.page = page
}
this.$get(that.url, that.example).then(function(response) {
if (response) {
that.result = response
} else {
that.result = {
rows: [],
total: 0
}
}
})
}
add() {
if (this.$refs.update != null) {
this.$refs.update.clearValidate()
}
this.param = {};
this.flag.add = true
this.flag.modify = true
}
modify(row) {
if (this.$refs.update != null) {
this.$refs.update.clearValidate()
}
this.flag.add = false
this.param = JSON.parse(JSON.stringify(row))
this.flag.modify = true
}
delate(row) {
const that = this
const msg = this.name ? this.name : "记录"
this.$confirm('确定要删除该' + msg + '吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$delete(that.url, row).then(function(response) {
if (response) {
that.query()
that.$emit('change');
}
})
}).catch(() => {
that.showMessage('info', '已取消删除')
})
}
confirm() {
const that = this
this.$refs.update.validate((valid) => {
if (valid) {
const method = that.flag.add ? that.$post : that.$put;
method(that.url, that.param).then(function(response) {
if (response) {
that.query()
that.flag.modify = false
that.$emit('change');
}
})
} else {
return false
}
})
}
getValue(dictKey, value) {
if (!dictKey) {
return value;
}
const dict = this.$store.state.dict[dictKey]
if (dict == null) {
return value;
}
return dict[value] == null ? value : dict[value]
}
created() {
this.$set(this.flag, 'add', false);
this.$set(this.flag, 'modify', false);
this.query();
if (this.flag.update) {
this.actionWidth += 60;
}
if (this.flag.del) {
this.actionWidth += 60;
}
const that = this;
if (this.actions) {
this.actions.forEach(i => {
that.actionWidth += i.width;
})
}
}
@Watch('flag.modify')
modifyChang(n, o) {
if (!n) {
this.param = {};
}
}
mounted() {} // - 访DOM
beforeCreate() {} // -
beforeMount() {} // -
beforeUpdate() {} // -
updated() {} // -
beforeDestroy() {} // -
destroyed() {} // -
activated() {} // keep-alive
}
</script>
<style scoped>
/* #mamageTemp {
padding: 20px 20px 0px 20px;
} */
</style>

62
vue/src/components/template/modifyItem.vue

@ -0,0 +1,62 @@ @@ -0,0 +1,62 @@
<template>
<div id="modifyItem">
<!-- <el-form-item :label="prop.name" :prop="prop.code" v-if="!prop.hide && (!prop.addHide || !flag.add)"> -->
<el-form-item :label="prop.name" :prop="prop.code"
v-if="!(prop.hide || (flag.add && prop.addHide) || (!flag.add && prop.modifyHide))">
<el-select v-if="prop.dict" v-model="param[prop.code]" :disabled="!flag.add && prop.readOnly" filterable
:placeholder="'请选择'+prop.name" clearable>
<el-option v-if="prop.dict == 'order'" v-for="index in 99" :key="index" :value="index+''" :label="index" />
<el-option v-if="prop.dict != 'order'" v-for="(val, key, i) in $store.state.dict[prop.dict]" :key="key"
:value="prop.int ? parseInt(key):key+''" :label="val" />
</el-select>
<el-select v-if="prop.state" :multiple="prop.multiple" v-model="param[prop.code]"
:disabled="!flag.add && prop.readOnly" :placeholder="'请选择'+prop.name" filterable clearable>
<el-option v-for="item in $store.state[prop.state]" :key="item.key" :value="item.key" :label="item.value" />
</el-select>
<el-autocomplete v-if="prop.autocomplete" v-model="param[prop.code]" :fetch-suggestions="prop.getData" onKeypress="return(event.keyCode != 32)"
:placeholder="'请输入'+prop.name" clearable>
<template slot-scope="{ item }">
<div>{{ item.value + '--' + item[prop.labelKey] }}</div>
</template>
</el-autocomplete>
<el-input v-if="!prop.dict && !prop.state && !prop.autocomplete" v-model="param[prop.code]" onKeypress="return(event.keyCode != 32)"
:disabled="!flag.add && prop.readOnly" :placeholder="prop.desc ? prop.desc : '请输入'+prop.name" clearable />
</el-form-item>
</div>
</template>
<script>
import {
Vue,
Component,
Prop
} from "vue-property-decorator";
@Component
export default class ModifyItem extends Vue {
@Prop({
type: Object
}) prop;
@Prop({
type: Object
}) flag;
@Prop({
type: Object
}) param;
created() {} // - 访this
mounted() {} // - 访DOM
beforeCreate() {} // -
beforeMount() {} // -
beforeUpdate() {} // -
updated() {} // -
beforeDestroy() {} // -
destroyed() {} // -
activated() {} //keep-alive
}
</script>
<style scoped>
</style>

17
vue/src/main.ts

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
import Vue from 'vue'
import './static/plugins/axios'
import App from './views/main/App.vue'
import './static/plugins/element'
import router from './router'
import store from './store'
import VueClipboard from 'vue-clipboard2'
Vue.config.productionTip = false
Vue.use(VueClipboard)
const vue = new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
export default vue

37
vue/src/router/index.ts

@ -0,0 +1,37 @@ @@ -0,0 +1,37 @@
import Vue from 'vue'
import VueRouter, { RouteConfig } from 'vue-router'
Vue.use(VueRouter)
const routes: Array<RouteConfig> = [
{
path: '/',
redirect: '/home'
},{
path: '/home',
name: 'home',
component: () => import('../views/biz/picture.vue')
},{
path: '/login',
name: 'Login',
component: () => import('../views/main/login.vue')
},{
path: '/config',
name: 'Config',
component: () => import('../views/sys/config.vue')
}
]
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push (location) {
return originalPush.call(this, location).catch(err => err)
}
const router = new VueRouter({
routes
})
router.afterEach((to, from) => {
// ...
})
export default router

13
vue/src/shims-tsx.d.ts vendored

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
import Vue, { VNode } from 'vue'
declare global {
namespace JSX {
// tslint:disable no-empty-interface
interface Element extends VNode {}
// tslint:disable no-empty-interface
interface ElementClass extends Vue {}
interface IntrinsicElements {
[elem: string]: any;
}
}
}

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

@ -0,0 +1,4 @@ @@ -0,0 +1,4 @@
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}

289
vue/src/static/plugins/axios.ts

@ -0,0 +1,289 @@ @@ -0,0 +1,289 @@
/* eslint-disable */
import Vue from "vue";
import { Message } from "element-ui";
import axios from "axios";
import store from "@/store";
import loading from "./element"
const config = {
// baseURL: process.env.VUE_APP_BASE_URL || process.env.apiUrl || "",
baseURL: process.env.VUE_APP_BASE_URL ? "/api" : "",
timeout: 60 * 1000,
withCredentials: true // Check cross-site Access-Control
};
const _axios = axios.create(config);
let curMsg: any;
// Add a request interceptor
_axios.interceptors.request.use(
function(config) {
const time = new Date().getTime().toString();
if(config.params){
delEmpty(config.params);
config.params.t = time;
}
if (config.data && typeof config.data === Object) {
delEmpty(config.params);
config.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) {
if (!response.data.message) {
return { success: true, data: response.data };
}
return response.data;
},
function(error) {
return Promise.reject(error);
}
);
function post(url, data = {}, noMsg, noLoading) {
return new Promise((resolve, reject) => {
if(!noLoading){
loading.start();
}
_axios.post(url, data).then(
(response: any) => {
handResponse(response, resolve, noMsg, noLoading);
},
err => {
handError(err, reject, noMsg, noLoading);
}
);
});
}
function get(url, data = {}, noMsg, noLoading) {
return new Promise((resolve, reject) => {
if(!noLoading){
loading.start();
}
_axios.get(url, {params:data}).then(
(response: any) => {
handResponse(response, resolve, noMsg, noLoading);
},
err => {
handError(err, reject, noMsg, noLoading);
}
);
});
}
function put(url, data = {}, noMsg, noLoading) {
return new Promise((resolve, reject) => {
if(!noLoading){
loading.start();
}
console.log(url);
_axios.put(url, data).then(
(response: any) => {
handResponse(response, resolve, noMsg, noLoading);
},
err => {
handError(err, reject, noMsg, noLoading);
}
);
});
}
function delate(url, data = {}, noMsg, noLoading) {
return new Promise((resolve, reject) => {
if(!noLoading){
loading.start();
}
_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){
loading.stop();
}
if (response.success) {
if (response.message) {
showMessage("success", response.message, false, noMsg);
resolve(true);
} else {
resolve(response.data);
}
} else {
if (response.message == "session timeout") {
store.commit("logout");
}
if (response.message == "no permission") {
response.message = "您暂无权访问,请联系管理员添加";
}
if (response.message.indexOf("no permission for ") == 0) {
response.message = "此权限尚未开放,请勿越权访问";
}
showMessage("error", response.message, false, noMsg);
console.log(response.error);
resolve(false);
}
}
function handError(err, reject, noMsg, noLoading){
if(!noLoading){
loading.stop();
}
showMessage("error", "后台连接失败,网络异常!", false, noMsg);
reject(err);
}
function upload(file, url){
return new Promise((resolve, reject) => {
let param = new FormData() // 创建form对象
param.append('file', file) // 通过append向form对象添加数据
param.append('chunk', '0') // 添加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);
}
);
});
}
function download(fileName, url, data = {}, callBack) {
loading.start();
_axios({
method: "post",
url: url, // 请求地址
data: data, // 参数
responseType: "blob" // 表明返回服务器返回的数据类型
}).then(
response => {
loading.stop();
const data = response.data;
const reader = new FileReader() as any;
reader.readAsText(data);
reader.onload = function() {
try {
const result = JSON.parse(reader.result);
if (typeof result === "object") {
showMessage("error", result.message, false, false);
if (callBack != null) {
callBack(false);
}
return;
}
} catch (err) {}
const blob = new Blob([data], {
type: "application/vnd.ms-excel"
});
if (window.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 => {
loading.stop();
showMessage("error", "下载失败,网络异常!", false, false);
if (callBack != null) {
callBack(false);
}
}
);
}
function showMessage(type, info, unClose, noMsg) {
const vue = Vue as any;
if (noMsg) {
return;
}
if (curMsg != null) {
curMsg.close();
}
const tmp = Message({
type: type,
showClose: true,
message: info,
duration: 3000
});
if (!unClose) {
curMsg = tmp;
}
}
function showNotify(type, title, message, position) {
const vue = Vue as any;
if (position == null) {
position = "top-right";
}
vue.$notify({
title: title,
type: type,
message: message,
position: position,
duration: 0
});
}
Vue.prototype.$post = post;
Vue.prototype.$get = get;
Vue.prototype.$put = put;
Vue.prototype.$delete = delate;
Vue.prototype.$upload = upload;
Vue.prototype.$download = download;
Vue.prototype.$showMessage = showMessage;
Vue.prototype.$showNotify = showNotify;
export default {
post,
get,
put,
delate,
upload,
download,
showMessage,
showNotify
}

47
vue/src/static/plugins/element.ts

@ -0,0 +1,47 @@ @@ -0,0 +1,47 @@
import Vue from 'vue'
import Element from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'
let count = 0
let ld = null
const start = () => {
if (count === 0) {
ld = Element.Loading.service({
lock: true,
text: 'Loading',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.3)'
})
}
count++
}
const stop = () => {
if (count <= 0) {
return
}
count--
if (count === 0) {
ld.close()
}
}
Vue.prototype.$start = start
Vue.prototype.$stop = stop
Vue.use(Element)
const icons = [
'el-icon-s-platform', 'el-icon-user-solid', 'el-icon-user', 'el-icon-unlock', 'el-icon-notebook-2', 'el-icon-school',
'el-icon-check', 'el-icon-video-play', 'el-icon-s-tools', 'el-icon-setting', 'el-icon-message-solid', 'el-icon-bell',
'el-icon-mobile-phone', 'el-icon-map-location', 'el-icon-location', 'el-icon-document', 'el-icon-s-check', 'el-icon-data-analysis',
'el-icon-view', 'el-icon-s-home', 'el-icon-office-building', 'el-icon-picture', 'el-icon-odometer', 'el-icon-s-data',
'el-icon-tickets', 'el-icon-delete-solid', 'el-icon-phone-outline', 'el-icon-phone', 'el-icon-star-on', 'el-icon-star-off',
'el-icon-warning', 'el-icon-upload', 'el-icon-download', 'el-icon-s-custom', 'el-icon-house', 'el-icon-message', 'el-icon-thumb',
'el-icon-search', 'el-icon-collection'
]
export default {
start,
stop,
icons
}

56
vue/src/static/plugins/websocket.ts

@ -0,0 +1,56 @@ @@ -0,0 +1,56 @@
import store from "@/store";
import {showMessage} from "./axios";
let websock = null;
let onMessage = "";
let connect = false;
function init(url, cb) {
if (typeof(WebSocket) === "undefined") {
showMessage("error", "您的浏览器不支持socket");
return;
}
onMessage = cb;
websock = new WebSocket(url);
websock.onopen = websockOpen;
websock.onclose = websockClose;
websock.onerror = websockError;
websock.onmessage = websockMessage;
}
function send(msg) {
if (websock == null || !connect) {
setTimeout(() => send(msg), 500)
}else{
websock.send(JSON.stringify(msg));
}
}
function close(){
if(websock){
websock.close();
}
}
function websockOpen() {
connect = true;
}
function websockClose() {
connect = false;
store.commit("logout");
}
function websockError() {
showMessage("error", "后台通讯失败,请联系相关人员处理");
}
function websockMessage(e) {
const data = JSON.parse(e.data);
store.commit(onMessage, data);
}
export default {
init,
send,
close
}

139
vue/src/static/tool/BMapUtil.js

@ -0,0 +1,139 @@ @@ -0,0 +1,139 @@
import http from "../plugins/axios";
const fakePath = require("@/assets/image/fake.png");
const fakeIcon = new BMap.Icon(fakePath, new BMap.Size(21, 31), {
offset: new BMap.Size(10, 25)
});
const locatePath = require("@/assets/image/locate.png");
const locateIcon = new BMap.Icon(locatePath, new BMap.Size(21, 31), {
offset: new BMap.Size(10, 25)
});
const dronePath = require("@/assets/drone/drone.gif");
const droneIcon = new BMap.Icon(dronePath, new BMap.Size(80, 80), {
offset: new BMap.Size(10, 25)
});
const circleOptions = {
strokeColor: 'yellow',
strokeWeight: 1,
strokeOpacity: 0.5,
fillColor: 'yellow',
fillOpacity: 0.1
};
//圆的半径,单位为米
let radius = 100;
http.get("public/get/circle/conf").then(rsp => {
if (rsp) {
radius = rsp[0];
circleOptions.fillOpacity = rsp[1];
}
})
function paintLocation(map, data) {
for (let i = 0; i < data.length; i++) {
const loc = data[i];
if (loc.lng == 0 || loc.lat == 0) {
continue;
}
const point = new BMap.Point(loc.lng, loc.lat);
const marker = createMarker(loc);
const text = "手机号码:--<br\>采集时间:" + loc.time + "<br\>经纬度:" + loc.lng + ", " + loc.lat + "<br\>场强:" + loc.rssi;
marker.addEventListener("click", function() {
map.closeInfoWindow();
openInfo(map, loc, text)
});
map.addOverlay(marker);
}
}
function paintTrack(map, data) {
for (let i = 0; i < data.length; i++) {
const track = data[i];
const point = new BMap.Point(track.lng, track.lat);
const marker = createMarker(track, i);
const text = "手机号码:--<br\>采集时间:" + track.time + "<br\>经度:" + track.lng + "<br\>纬度:" + track.lat;
marker.addEventListener("click", function() {
map.closeInfoWindow();
openInfo(map, track, text)
});
if (marker.label) {
marker.label.addEventListener("click", function() {
map.closeInfoWindow();
openInfo(map, track, text)
});
map.addOverlay(marker.label);
}
map.addOverlay(marker);
if (i == data.length - 1) {
map.centerAndZoom(point, 18);
openInfo(map, track, text)
}
}
}
function createMarker(row, i) {
const point = new BMap.Point(row.lng, row.lat);
let marker;
if (row.rssi) {
marker = new BMap.Marker(point, {
icon: locateIcon
});
setLabel(marker, row.rssi - 1);
} else if (row.trust == 0) {
// marker = new BMap.Circle(point, radius, circleOptions);
// marker.label = new BMap.Marker(marker.getCenter(), {
// icon: fakeIcon
// });
marker = new BMap.Marker(point, {
icon: fakeIcon
});
marker.label = new BMap.Circle(marker.getPosition(), radius, circleOptions);
setLabel(marker, i);
} else {
marker = new BMap.Marker(point);
setLabel(marker, i);
}
return marker;
}
function addLable(marker, i) {
setLabel(marker.label, i);
}
function setLabel(marker, i) {
const label = new BMap.Label(i + 1, {
offset: new BMap.Size(2, 5)
});
label.setStyle({
backgroundColor: "none",
color: "black",
border: '0px',
borderRadius: "50%",
height: "13px",
width: "13px"
});
marker.setLabel(label);
}
function openInfo(map, row, text) {
const center = new BMap.Point(row.lng, row.lat)
map.setCenter(center);
const opts = {
width: 250,
height: 100,
title: 'IMSI:' + row.imsi
}
if (!text) {
text = "手机号码:--<br\>初次上报时间:" + row.firstTime + "<br\>最近上报时间:" + row.time + "<br\>经纬度:" + row.lng + "," + row.lat;
}
const infoWindow = new BMap.InfoWindow(text, opts);
map.openInfoWindow(infoWindow, center);
}
export {
createMarker,
paintTrack,
openInfo,
droneIcon,
paintLocation
}

55
vue/src/static/tool/dateUtil.js

@ -0,0 +1,55 @@ @@ -0,0 +1,55 @@
const weeks = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六']
const formatterDateTime = function (date, type) {
if (date == null) {
date = new Date()
}
const month = date.getMonth() < 9 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
const day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
const hour = date.getHours() < 10 ? '0' + date.getHours() : date.getHours()
const minute = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes()
const second = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds()
switch (type) {
case 0:
return date.getFullYear() + '年' + month + '月' + day + '日' + hour + '⑩' + minute + '分' + second + '秒'
}
return date.getFullYear() + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second
}
const formatterDate = function (date, type) {
if (date == null) {
date = new Date()
}
const month = date.getMonth() < 9 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
const day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate()
switch (type) {
case 0:
return date.getFullYear() + '年' + month + '月' + day + '日'
}
return date.getFullYear() + '-' + month + '-' + day
}
const formatterTime = function (date, type) {
if (date == null) {
date = new Date()
}
const month = date.getMonth() < 9 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1
switch (type) {
case 0:
return hour + '⑩' + minute + '分' + second + '秒'
}
return date.getFullYear() + '-' + month
}
const formatterWeekDay = function (date, type) {
if (date == null) {
date = new Date()
}
return weeks[date.getDay()]
}
export {
formatterDateTime,
formatterDate,
formatterTime,
formatterWeekDay
}

80
vue/src/static/tool/ruleControl.js

@ -0,0 +1,80 @@ @@ -0,0 +1,80 @@
import * as validate from './validate'
function getValidator(required, validateName, number) {
const arr = []
if (required != null) {
arr.push({
required: true,
message: required,
trigger: 'blur'
})
}
if (validateName != null) {
arr.push({
validator: validate[validateName],
trigger: 'blur'
})
}
if (number) {
arr.push({
type: 'number',
min: 0,
message: '请输入正确的数字',
trigger: 'blur'
})
}
return arr
}
const login = {
userId: getValidator('请输入用户名'),
password: getValidator('请输入密码')
}
const dictionary = {
code: getValidator('请输入字典编号'),
name: getValidator('请输入字典名称'),
visitedNetworkAddress: getValidator('请选择字典状态')
}
const app = {
name : getValidator('请输入APP名称'),
itemId : getValidator('请输入授权项编号'),
type : getValidator('请选择授权项类型')
}
const customer = {
code : getValidator('请输入客户编号'),
name : getValidator('请输入客户名称'),
contacter : getValidator('请输入联系人'),
phone : getValidator('请输入联系人号码')
}
const author = {
custCode : getValidator('请选择客户'),
fingerprint : getValidator('请输入指纹信息'),
key1 : getValidator('请选择Code1'),
key1 : getValidator('请选择Code2')
}
const client = {
authorize : getValidator('请选择授权中心'),
appId : getValidator('请选择授权APP'),
num : getValidator('请输入授权数量'),
keyId : getValidator('请选择户端秘钥')
}
const key = {
type : getValidator('请选择秘钥类型'),
remark : getValidator('请输入秘钥名称')
}
export {
login,
dictionary,
app,
customer,
author,
client,
key
}

233
vue/src/static/tool/validate.js

@ -0,0 +1,233 @@ @@ -0,0 +1,233 @@
function password (rule, value, callback) {
if (value == null || value === '') {
callback(new Error('请输入密码'))
} else if (value.length < 8 || value.length > 16) {
callback(new Error('密码长度为:8~16位'))
} else if (!new RegExp('^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).*$').test(value)) {
callback(new Error('密码至少包含大小写字母和数字'))
} else if (!new RegExp('^[^ ]+$').test(value)) {
callback(new Error('密码不能包含空格'))
}
callback()
}
function code (rule, value, callback) {
if (value == null || value == '') {
callback(new Error('验证码不能为空'))
} else if (!new RegExp('^[0-9]+$').test(value)) {
callback(new Error('验证码只能为数字'))
}
callback()
}
function email (rule, value, callback) {
if (value != null && value.length != 0) {
if (
!new RegExp(
/^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/
).test(value)
) {
callback(new Error('请输入正确的电子邮箱'))
}
}
callback()
}
function phone (rule, value, callback) {
if (value != null && value.length != 0) {
if (
!new RegExp(
'^(0|86|17951)?(13[0-9]|15[012356789]|17[0135678]|18[0-9]|14[57])[0-9]{8}$'
).test(value)
) {
callback(new Error('请输入正确的手机号码'))
}
}
callback()
}
function imsi (rule, value, callback) {
// if(value != null && value.length != 0){
// }
callback()
}
function userName (rule, value, callback) {
if (value != null && value.length != 0) {
if (!new RegExp('^[a-zA-Z0-9]{6,16}$').test(value)) {
callback(new Error('用户名由字母,数字组成,长度6-16之间'))
}
if (/(^\_)|(\__)|(\_+$)/.test(value)) {
callback(new Error("用户名首尾不能出现下划线'_'"))
}
if (!new RegExp('^[^ ]+$').test(value)) {
callback(new Error('用户名不能包含空格'))
}
}
callback()
}
function name (rule, value, callback) {
if (value != null && value.length != 0) {
if (!new RegExp('^[a-zA-Z\u4e00-\u9fa5]{1,10}$').test(value)) {
callback(new Error('姓名由汉字或字母组成,且长度不超过10'))
}
}
callback()
}
function idCard (rule, value, callback) {
if (value != null && value.length != 0) {
let iSum = 0
const aCity = {
11: '北京',
12: '天津',
13: '河北',
14: '山西',
15: '内蒙古',
21: '辽宁',
22: '吉林',
23: '黑龙江',
31: '上海',
32: '江苏',
33: '浙江',
34: '安徽',
35: '福建',
36: '江西',
37: '山东',
41: '河南',
42: '湖北',
43: '湖南',
44: '广东',
45: '广西',
46: '海南',
50: '重庆',
51: '四川',
52: '贵州',
53: '云南',
54: '西藏',
61: '陕西',
62: '甘肃',
63: '青海',
64: '宁夏',
65: '新疆',
71: '台湾',
81: '香港',
82: '澳门',
91: '国外'
}
if (!/^\d{17}(\d|x)$/i.test(value)) {
callback(new Error('你输入的身份证长度或格式错误'))
}
value = value.replace(/x$/i, 'a')
if (aCity[parseInt(value.substr(0, 2))] == null) {
callback(new Error('你的身份证地区非法'))
}
const sBirthday =
value.substr(6, 4) +
'-' +
Number(value.substr(10, 2)) +
'-' +
Number(value.substr(12, 2))
const d = new Date(sBirthday.replace(/-/g, '/'))
if (
sBirthday !=
d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate()
) {
callback(new Error('身份证上的出生日期非法'))
}
for (let i = 17; i >= 0; i--) { iSum += (Math.pow(2, i) % 11) * parseInt(value.charAt(17 - i), 11) }
if (iSum % 11 != 1) {
callback(new Error('你输入的身份证号非法'))
}
}
callback()
}
function devName (rule, value, callback) {
console.log(rule)
if (value == null || value.length == 0 || value == '') {
callback(new Error('请输入设备名称'))
}
if (!new RegExp('^[a-zA-Z0-9\u4e00-\u9fa5]{1,10}$').test(value)) {
callback(new Error('设备名称由汉字,字母,数字组成,且长度不能超过10'))
}
callback()
}
function devId (rule, value, callback) {
if (value == null || value.length == 0 || value == '') {
callback(new Error('请输入设备编号'))
}
if (!new RegExp(/^[1-9]\d{0,8}$/).test(value)) {
callback(new Error('设备编号由数字组成,且长度不能超过9,首位不能为0'))
}
callback()
}
function ipv4 (rule, value, callback) {
if (value != null && value.length != 0) {
if (
!new RegExp(/^((25[0-5]|2[0-4]\d|[01]?\d\d?)($|(?!\.$)\.)){4}$/).test(
value
)
) {
callback(new Error('错误的IPV4地址格式'))
}
}
callback()
}
function lon (rule, value, callback) {
if (
!new RegExp(
/^-?((0|1?[0-7]?[0-9]?)(([.][0-9]{1,6})?)|180(([.][0]{1,6})?))$/
).test(value)
) {
callback(new Error('请输入正确的经度'))
}
callback()
}
function lat (rule, value, callback) {
if (
!new RegExp(
/^-?((0|[1-8]?[0-9]?)(([.][0-9]{1,6})?)|90(([.][0]{1,6})?))$/
).test(value)
) {
callback(new Error('请输入正确的纬度'))
}
callback()
}
function number (rule, value, callback) {
if (!new RegExp(/^[1-9][0-9]*$/).test(value)) {
callback(new Error('请输入正整数'))
}
const num = parseInt(value)
if (rule.min != null && num < rule.min) {
callback(new Error('输入最小值为' + rule.min))
}
if (rule.max != null && num > rule.max) {
callback(new Error('输入最大值为' + rule.max))
}
callback()
}
export {
password,
code,
email,
phone,
imsi,
userName,
name,
idCard,
devName,
devId,
ipv4,
lon,
lat,
number
}

13
vue/src/store/actions.js

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
export default {
init(context){
context.commit('getUserInfo');
context.commit('getCfg');
context.commit('updateState', {
prop: 'showHeader',
value: true
})
context.commit('initSizes')
}
}

15
vue/src/store/index.ts

@ -0,0 +1,15 @@ @@ -0,0 +1,15 @@
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations.js'
import actions from './actions.js'
import modules from './modules.js'
Vue.use(Vuex)
export default new Vuex.Store({
state,
mutations,
actions,
modules
})

3
vue/src/store/modules.js

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
export default class {
}

175
vue/src/store/mutations.js

@ -0,0 +1,175 @@ @@ -0,0 +1,175 @@
import http from '../static/plugins/axios'
import websocket from '../static/plugins/websocket'
import vue from '../main'
import Vue from 'vue'
let interval = null;
const urls = {
info: 'public/info',
dict: 'public/get/dict',
logout: 'public/logout',
host: 'public/host'
}
function stop() {
if (interval == null) {
return
}
clearInterval(interval)
interval = null
}
function start() {
stop()
interval = setInterval(() => {
http.get(urls.info, null, true, true).then(rsp => {
if (!rsp) {
stop()
vue.$router.push('/login')
}
})
}, 5000)
}
function open(userName) {
let url = "ws://";
if (process.env.VUE_APP_BASE_URL) {
url += process.env.VUE_APP_BASE_URL;
} else {
url += window.location.hostname;
const port = window.location.port;
if (port != null && port != '') {
url += (":" + port);
}
}
url = url + "/webSocket/" + userName;
websocket.init(url, "websockMessage");
}
export default {
updateState(state, param) {
state[param.prop] = param.value;
},
initSizes(state) {
const height = window.innerHeight;
const width = window.innerWidth;
const loginHeight = 350;
const loginWidth = 450;
const navHead = state.showHeader ? 80 : 0;
const map = {
medium: 50,
small: 42,
mini: 35
};
const head = map[state.size] ? map[state.size] : 60;
const pd = 0;
const page = 37;
Vue.set(state.sizes, 'fullHeight', height + 'px');
Vue.set(state.sizes, 'loginHeight', loginHeight + 'px');
Vue.set(state.sizes, 'loginWidth', loginWidth + 'px');
Vue.set(state.sizes, 'loginTop', (height - loginHeight) * 0.4 + 'px');
Vue.set(state.sizes, 'loginLeft', (width - loginWidth) * 0.49 + 'px');
Vue.set(state.sizes, 'headHeight', navHead + 'px');
Vue.set(state.sizes, 'mainHead', head + 'px');
Vue.set(state.sizes, 'mainHeight', height - navHead + 'px');
Vue.set(state.sizes, 'tableHei', height - navHead - head - pd);
Vue.set(state.sizes, 'pTableHei', state.sizes.tableHei - page);
},
getUserInfo(state) {
http.get(urls.info).then(response => {
if (response) {
state.userInfo = response;
state.menuGroups = [{
name: '主页',
id: 'home',
child: []
}];
if (response.permission == '1') {
state.menuGroups.push({
name: '系统管理',
id: 'sys',
child: [{
name: '配置管理',
id: 'config'
}, {
name: '用户管理',
id: 'user'
}]
})
}
start()
// open(response.userId);
} else {
vue.$router.push('/login')
}
})
.catch(() => {
vue.$router.push('/login')
})
},
websockMessage(state, data) {
const handler = state.handlers[data.type];
if (handler != null) {
handler(data.data);
}
},
getCfg(state) {
http.get(urls.host).then(rsp => {
state.host = rsp;
});
},
getDictMap(state, codes, callBack) {
if (codes == null) {
return
}
const arr = []
for (let i = 0; i < codes.length; i++) {
if (Object.keys(state.dict).indexOf(codes[i]) == -1) {
arr.push(codes[i])
}
}
if (arr.length == 0) {
return
}
http.post(urls.dict, arr).then(function(response) {
if (response) {
for (let i = 0; i < arr.length; i++) {
const key = arr[i]
state.dict[key] = {}
const dicts = response[key]
if (dicts == null) {
continue
}
for (let j = 0; j < dicts.length; j++) {
const tmp = dicts[j]
const num = parseInt(tmp.code)
if (!isNaN(num)) {
state.dict[key][num] = tmp.name
} else {
state.dict[key][tmp.code] = tmp.name
}
}
}
if (callBack != null) {
callBack()
}
}
})
},
logout(state) {
http.get(urls.logout).then(response => {
if (response) {
vue.$router.push('/login')
state.userInfo = {}
}
});
websocket.close();
}
}

11
vue/src/store/state.js

@ -0,0 +1,11 @@ @@ -0,0 +1,11 @@
export default {
menuGroups: [],
sizes: {},
userInfo: {},
showHeader: true,
size: 'small',
dict: {},
handlers: {},
host: {},
network: "traHost"
}

243
vue/src/views/biz/picture.vue

@ -0,0 +1,243 @@ @@ -0,0 +1,243 @@
<template>
<div id="picture">
<MainHead name="图片" :flag="flag" :terms="terms" :example="example" @query="query(1)">
<template slot="title">
<span class="network-switch">
<div class="switch-label">网络环境:</div>
<el-switch v-model="$store.state.network" active-value="tertHost" inactive-value="traHost" :width="60" active-text="外网"
inactive-text="内网"></el-switch>
</span>
</template>
<template slot="header">
<el-upload class="upload-btn" :action="uploadUrl" :on-error="onError" :on-success="onSuccess" :multiple="false"
:show-file-list="false">
<el-button :size="$store.state.size" type="success" icon="el-icon-upload">
上传图片
</el-button>
</el-upload>
</template>
</MainHead>
<el-row class="image-list" :style="{ height: $store.state.sizes.pTableHei + 'px' }">
<el-col v-for="(item, index) in result.rows" :xs="8" :sm="7" :md="6" :xl="4">
<el-result icon="success" :title="item.remark ? item.remark : (index + 1).toString()"
:subTitle="item.time + ' ' + item.space + ' ' + item.remark">
<template slot="icon">
<el-image :src="'http//'+ $store.state.host[$store.state.network] + '/' + item.path">
</el-image>
</template>
<template slot="extra">
<el-button type="primary" size="mini" @click="copy(item)">复制</el-button>
<el-button type="info" size="mini" @click="remark(item)">备注</el-button>
<el-button type="danger" size="mini" @click="delate(item)">删除</el-button>
</template>
</el-result>
</el-col>
</el-row>
<el-pagination :current-page="example.page" :page-sizes="[10, 20, 50, 100]" :page-size="example.size"
layout="total, sizes, prev, pager, next, jumper" :total="result.total" @size-change="handleSizeChange"
@current-change="handleCurrentChange" />
</div>
</template>
<script>
import {
Vue,
Component,
Prop
} from "vue-property-decorator";
import Template from '@/components/template/manage.vue';
import MainHead from '@/components/template/mainHeader.vue';
@Component({
components: {
Template,
MainHead
}
})
export default class Picture extends Vue {
url = 'picture';
uploadUrl = (process.env.VUE_APP_BASE_URL ? "/api/" : "/") + this.url;
flag = {
list: true,
save: false,
update: false,
del: true,
title: true
}
terms = [{
code: 'remark',
desc: '备注'
}, {
code: 'startTime',
datePicker: true,
valueFormat: 'yyyy-MM-dd HH:mm:ss',
type: 'datetime',
desc: '开始时间'
}, {
code: 'endTime',
datePicker: true,
valueFormat: 'yyyy-MM-dd HH:mm:ss',
type: 'datetime',
desc: '结束时间'
}]
example = {
page: 1,
size: 20
};
result = {
rows: [],
total: 0
};
handleSizeChange(val) {
this.example.size = val
this.query()
}
handleCurrentChange(val) {
this.example.page = val
this.query()
}
query(page) {
const that = this
if (page != null) {
this.example.page = page
}
this.$get(that.url, that.example).then(function(response) {
if (response) {
that.result = response
} else {
that.result = {
rows: [],
total: 0
}
}
})
}
copy(row) {
const that = this;
const host = this.$store.state.host[this.$store.state.network];
const path = 'http://' + host + "/" + row.path;
this.$copyText(path).then(e => {
that.$showMessage("success", "复制成功");
}, e => {
that.$showMessage("success", "复制失败");
})
}
remark(row) {
const that = this
this.$prompt('请输入备注信息', '备注', {
confirmButtonText: '确定',
cancelButtonText: '取消',
inputPattern: /^[\S]{1,64}$/,
inputErrorMessage: '备注不能输入空格,且长度不超过64位',
distinguishCancelAndClose: true,
showClose: false,
beforeClose: (action, instance, done) => {
if (action === 'close') {
return
}
done()
}
}).then(({
value
}) => {
console.log(value);
const param = JSON.parse(JSON.stringify(row));
param.remark = value;
that.$put(that.url, param).then(rsp => {
if (rsp) {
row.remark = value;
}
});
}).catch(() => {})
}
delate(row) {
const that = this
this.$confirm('确定要删除该图片吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
this.$delete(that.url, row).then(function(response) {
if (response) {
that.query()
}
})
}).catch(() => {
that.showMessage('info', '已取消删除')
})
}
onError(err, file, fileList) {
that.$showMessage('error', file.name + "上传失败", true)
console.log(err);
}
onSuccess(response, file, fileList) {
const that = this;
if (response.success == false) {
that.$showMessage('error', response.message, true);
} else {
that.$showMessage('success', response.message, true)
}
this.$refs.upload.clearFiles();
}
getClipboardFiles(event) {
let items = event.clipboardData && event.clipboardData.items;
let file = null
if (items && items.length) {
// items
for (var i = 0; i < items.length; i++) {
if (items[i].type.indexOf('image') !== -1) {
file = items[i].getAsFile()
}
}
}
if (file) {
this.$confirm('是否要上传剪切板中复制的图片?', '提示', {
confirmButtonText: '是的',
cancelButtonText: '不用',
type: 'warning'
}).then(() => {
this.$upload(file, this.url);
}).catch(() => {})
}
}
created() {
this.query(1);
document.addEventListener('paste', this.getClipboardFiles);
} // - 访this
mounted() {} // - 访DOM
beforeCreate() {} // -
beforeMount() {} // -
beforeUpdate() {} // -
updated() {} // -
beforeDestroy() {
document.removeEventListener('paste', this.getClipboardFiles);
} // -
destroyed() {} // -
activated() {} //keep-alive
}
</script>
<style scoped>
.upload-btn {
padding-left: 10px;
}
.network-switch {
padding-left: 20px;
}
.switch-label {
float: left;
font-size: 16px;
font-weight: bold;
}
.image-list {
overflow-y: auto;
}
</style>

66
vue/src/views/main/App.vue

@ -0,0 +1,66 @@ @@ -0,0 +1,66 @@
<template>
<div id="app" :style="{ height: $store.state.sizes.height }">
<NavHeader v-show="$store.state.showHeader" class="app-head" :style="{ height: $store.state.sizes.headHeight+'px' }"
ref="header" />
<router-view class="app-main" :style="{ height: $store.state.sizes.mainHeight }" @init="init" />
</div>
</template>
<script>
import {
Component,
Prop,
Vue,
Watch
} from 'vue-property-decorator'
import NavHeader from '@/components/main/NavHeader';
import websoct from '@/static/plugins/websocket';
@Component({
components: {
NavHeader
}
})
export default class App extends Vue {
initSize() {
this.$store.commit('initSizes')
}
init() {
this.$store.dispatch('init');
this.$router.push('/home')
if (this.$refs.header) {
this.$refs.header.toHome()
}
}
created() {
this.init()
this.initSize()
window.onresize = this.initSize
}
mounted() {} // - 访DOM
beforeCreate() {} // -
beforeMount() {} // -
beforeUpdate() {} // -
updated() {} // -
beforeDestroy() {} // -
destroyed() {} // -
activated() {} // keep-alive
}
</script>
<style lang="scss">
@import '~@/assets/styles/base.scss';
@import '~@/assets/styles/size_small.scss';
// @import '~@/assets/styles/back_blue.scss';
.app-head {
padding: 0px !important;
}
.app-main {
padding: 0px 20px !important;
}
</style>

124
vue/src/views/main/login.vue

@ -0,0 +1,124 @@ @@ -0,0 +1,124 @@
<template>
<div id="login">
<div class="loginForm" :style="{ height:$store.state.sizes.loginHeight,width:$store.state.sizes.loginWidth,
'margin-top': $store.state.sizes.loginTop,'margin-left':$store.state.sizes.loginLeft}">
<el-form ref="login" class="login-form" :model="user" :rules="rules" hide-required-asterisk label-width="30px">
<div class="login-title">
图床卅
</div>
<br><br>
<el-form-item prop="userId">
<el-input v-model="user.userId" placeholder="用户名">
<el-button slot="prepend" icon="el-icon-user" />
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="user.password" type="password" placeholder="密码">
<el-button slot="prepend" icon="el-icon-key" />
</el-input>
</el-form-item>
<br>
<el-form-item>
<el-button class="login-btn" type="primary" @click="loginSubmit">登录</el-button>
<el-button style="float: right;" class="login-btn" type="primary" @click="reset">重置</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script lang="ts">
import {
Component,
Prop,
Vue
} from 'vue-property-decorator'
import md5 from 'js-md5'
import {
login
} from '@/static/tool/ruleControl.js'
@Component
export default class Login extends Vue {
marginTop = (window.innerHeight - 350) * 0.4 + 'px';
user = {
userId: null,
password: null
};
rules = login;
loginSubmit() {
const that = this
this.$refs.login.validate((valid) => {
if (valid) {
const param = JSON.parse(JSON.stringify(that.user))
param.password = md5(param.password)
that.$post('public/login', param).then(function(response) {
if (response == true) {
setTimeout(() => {
that.$emit("init");
}, 300)
}
})
} else {
return false
}
})
}
reset() {
this.user = {
userId: null,
password: null
}
}
init(b) {
this.$store.commit('updateState', {
prop: 'showHeader',
value: b
})
this.$store.commit('initSizes')
}
created() {
this.init(false);
const that = this;
document.onkeydown = function(e) {
let ev = e || event;
let key = ev.keyCode;
if (key == 13) {
that.loginSubmit();
}
};
}
}
</script>
<style scoped>
#login {
align-items: center;
background: none;
}
.loginForm {
background: none;
height: 350px;
width: 500px;
box-shadow: 0 0 25px #cac6c6 !important;
padding: 30px 30px 20px 0px;
}
.login-title {
margin: 0px 70px 20px;
font-size: 1.8rem;
text-align: center;
color: #2d3748;
}
.login-btn {
margin: 0px 30px;
width: 100px;
}
</style>

88
vue/src/views/sys/config.vue

@ -0,0 +1,88 @@ @@ -0,0 +1,88 @@
<template>
<div id="config">
<Template ref="template" name="配置" :url="url" :flag="flag" :terms="terms" :props="props" :rules="rules"
:actions="actions"></template>
</div>
</template>
<script>
import {
Vue,
Component,
Prop
} from "vue-property-decorator";
import Template from '@/components/template/manage.vue';
@Component({
components: {
Template
}
})
export default class Config extends Vue {
url = "sys/config";
flag = {
list: true,
save: false,
update: true,
del: false
}
rules = {};
terms = [{
code: 'cfgName',
desc: '配置名称'
}];
props = [{
code: 'cfgKey',
name: '配置key',
width: 150,
readOnly: true
}, {
code: 'cfgName',
name: '配置名称',
width: 150
}, {
code: 'cfgValue',
name: '配置值',
width: 150
}, {
code: 'cfgType',
name: '配置类型',
width: 150,
readOnly: true
}, {
code: 'cfgDesc',
name: '配置描述',
width: 200
}, {
code: 'updateTime',
name: '最近更新时间',
width: 170,
hide: true
}, {
code: 'updateUser',
name: '最近更新用户',
width: 160,
hide: true
// }, {
// code: 'cfgStatus',
// name: '',
// dict: 'active_status',
// width: 120
}];
actions = [];
created() {
// this.$store.commit('getDictMap', ['active_status']);
} // - 访this
mounted() {} // - 访DOM
beforeCreate() {} // -
beforeMount() {} // -
beforeUpdate() {} // -
updated() {} // -
beforeDestroy() {} // -
destroyed() {} // -
activated() {} //keep-alive
}
</script>
<style scoped>
</style>

39
vue/tsconfig.json

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
{
"compilerOptions": {
"target": "esnext",
"module": "esnext",
"strict": false,
"jsx": "preserve",
"importHelpers": true,
"moduleResolution": "node",
"experimentalDecorators": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"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"
]
}

39
vue/vue.config.js

@ -0,0 +1,39 @@ @@ -0,0 +1,39 @@
module.exports = {
lintOnSave: false,
publicPath: '/',
chainWebpack: config => {
config.module
.rule('images')
.use('url-loader')
.loader('url-loader')
.tap(options => Object.assign(options, { limit: 1 }))
},
configureWebpack: {
externals: {
'BMap': "BMap"
},
resolve: {
alias: {
'assets': '@/assets',
'common': '@/common',
'components': '@/components',
'network': '@/network',
'views': '@/views',
'plugins': '@/plugins',
}
}
},
devServer: {
proxy: {
'/api': {
target: 'http://'+process.env.VUE_APP_BASE_URL,
// 允许跨域
changeOrigin: true,
ws: true,
pathRewrite: {
'^/api': ''
}
}
}
}
}

9363
vue/yarn.lock

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