# 后台手册

# 分页实现

# 前端调用实现

1、前端定义分页流程

// 一般在查询参数中定义分页变量
queryParams: {
  pageNum: 1,
  pageSize: 10
},

// 页面添加分页组件,传入分页变量
<pagination
  v-show="total>0"
  :total="total"
  :page.sync="queryParams.pageNum"
  :limit.sync="queryParams.pageSize"
  @pagination="getList"
/>

// 调用后台方法,传入参数 获取结果
listUser(this.queryParams).then(response => {
    this.userList = response.rows;
    this.total = response.total;
  }
);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 后台逻辑实现

参考后台逻辑实现

# 导入导出

在实际开发中经常需要使用导入导出功能来加快数据的操作。在项目中可以使用注解来完成此项功能。 在需要被导入导出的实体类属性添加@Excel注解,目前支持参数如下:

# 注解参数说明

参数 类型 默认值 描述
sort int Integer.MAX_VALUE 导出时在excel中排序,值越小越靠前
name String 导出到Excel中的名字
dateFormat String 日期格式, 如: yyyy-MM-dd
dictType String 如果是字典类型,请设置字典的type值 (如: sys_user_sex)
readConverterExp String 读取内容转表达式 (如: 0=男,1=女,2=未知)
separator String , 分隔符,读取字符串组内容
scale int -1 BigDecimal 精度 默认:-1(默认不开启BigDecimal格式化)
roundingMode int BigDecimal.ROUND_HALF_EVEN BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN
celltype Enum Type.STRING 导出类型(0数字 1字符串 2图片)
height String 14 导出时在excel中每个列的高度 单位为字符
width String 16 导出时在excel中每个列的宽 单位为字符
suffix String 文字后缀,如% 90 变成90%
defaultValue String 当值为空时,字段的默认值
prompt String 提示信息
combo String Null 设置只能选择不能输入的列内容
headerBackgroundColor Enum IndexedColors.GREY_50_PERCENT 导出列头背景色IndexedColors.XXXX
headerColor Enum IndexedColors.WHITE 导出列头字体颜色IndexedColors.XXXX
backgroundColor Enum IndexedColors.WHITE 导出单元格背景色IndexedColors.XXXX
color Enum IndexedColors.BLACK 导出单元格字体颜色IndexedColors.XXXX
targetAttr String 另一个类中的属性名称,支持多级获取,以小数点隔开
isStatistics boolean false 是否自动统计数据,在最后追加一行统计数据总和
type Enum Type.ALL 字段类型(0:导出导入;1:仅导出;2:仅导入)
align Enum HorizontalAlignment.CENTER 导出对齐方式HorizontalAlignment.XXXX
handler Class ExcelHandlerAdapter.class 自定义数据处理器
args String[] {} 自定义数据处理器参数

# 导出实现流程

1、前端调用方法(参考如下)

// 查询参数 queryParams
queryParams: {
  pageNum: 1,
  pageSize: 10,
  userName: undefined
},

// 导出接口exportUser
import { exportUser } from "@/api/system/user";

/** 导出按钮操作 */
handleExport() {
  const queryParams = this.queryParams;
  this.$confirm('是否确认导出所有用户数据项?', "警告", {
	  confirmButtonText: "确定",
	  cancelButtonText: "取消",
	  type: "warning"
	}).then(function() {
	  return exportUser(queryParams);
	}).then(response => {
	  this.download(response.msg);
	}).catch(function() {});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

2、添加导出按钮事件

<el-button
  type="warning"
  icon="el-icon-download"
  size="mini"
  @click="handleExport"
>导出</el-button>
1
2
3
4
5
6

3、在实体变量上添加@Excel注解

@Excel(name = "用户序号", prompt = "用户编号")
private Long userId;

@Excel(name = "用户名称")
private String userName;
	
@Excel(name = "用户性别", readConverterExp = "0=男,1=女,2=未知")
private String sex;

@Excel(name = "用户头像", cellType = ColumnType.IMAGE)
private String avatar;

@Excel(name = "帐号状态", dictType = "sys_normal_disable")
private String status;

@Excel(name = "最后登陆时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
private Date loginDate;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

4、在Controller添加导出方法







 
 


@Log(title = "用户管理", businessType = BusinessType.EXPORT)
@PreAuthorize("@ss.hasPermi('system:user:export')")
@GetMapping("/export")
public AjaxResult export(SysUser user)
{
	List<SysUser> list = userService.selectUserList(user);
	ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
	return util.exportExcel(list, "用户数据");
}
1
2
3
4
5
6
7
8
9

# 导入实现流程

1、前端调用方法(参考如下)

import { getToken } from "@/utils/auth";

// 用户导入参数
upload: {
  // 是否显示弹出层(用户导入)
  open: false,
  // 弹出层标题(用户导入)
  title: "",
  // 是否禁用上传
  isUploading: false,
  // 是否更新已经存在的用户数据
  updateSupport: 0,
  // 设置上传的请求头部
  headers: { Authorization: "Bearer " + getToken() },
  // 上传的地址
  url: process.env.VUE_APP_BASE_API + "/system/user/importData"
},

// 导入模板接口importTemplate
import { importTemplate } from "@/api/system/user";

/** 导入按钮操作 */
handleImport() {
  this.upload.title = "用户导入";
  this.upload.open = true;
},
/** 下载模板操作 */
importTemplate() {
  importTemplate().then(response => {
	this.download(response.msg);
  });
},
// 文件上传中处理
handleFileUploadProgress(event, file, fileList) {
  this.upload.isUploading = true;
},
// 文件上传成功处理
handleFileSuccess(response, file, fileList) {
  this.upload.open = false;
  this.upload.isUploading = false;
  this.$refs.upload.clearFiles();
  this.$alert(response.msg, "导入结果", { dangerouslyUseHTMLString: true });
  this.getList();
},
// 提交上传文件
submitFileForm() {
  this.$refs.upload.submit();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48

2、添加导入按钮事件

<el-button
  type="info"
  icon="el-icon-upload2"
  size="mini"
  @click="handleImport"
>导入</el-button>
1
2
3
4
5
6

3、添加导入前端代码

<!-- 用户导入对话框 -->
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px">
  <el-upload
	ref="upload"
	:limit="1"
	accept=".xlsx, .xls"
	:headers="upload.headers"
	:action="upload.url + '?updateSupport=' + upload.updateSupport"
	:disabled="upload.isUploading"
	:on-progress="handleFileUploadProgress"
	:on-success="handleFileSuccess"
	:auto-upload="false"
	drag
  >
	<i class="el-icon-upload"></i>
	<div class="el-upload__text">
	  将文件拖到此处,或
	  <em>点击上传</em>
	</div>
	<div class="el-upload__tip" slot="tip">
	  <el-checkbox v-model="upload.updateSupport" />是否更新已经存在的用户数据
	  <el-link type="info" style="font-size:12px" @click="importTemplate">下载模板</el-link>
	</div>
	<div class="el-upload__tip" style="color:red" slot="tip">提示:仅允许导入“xls”或“xlsx”格式文件!</div>
  </el-upload>
  <div slot="footer" class="dialog-footer">
	<el-button type="primary" @click="submitFileForm">确 定</el-button>
	<el-button @click="upload.open = false">取 消</el-button>
  </div>
</el-dialog>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

4、在实体变量上添加@Excel注解,默认为导出导入,也可以单独设置仅导入Type.IMPORT

@Excel(name = "用户序号")
private Long id;

@Excel(name = "部门编号", type = Type.IMPORT)
private Long deptId;

@Excel(name = "用户名称")
private String userName;

/** 导出部门多个对象 */
@Excels({
	@Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT),
	@Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT)
})
private SysDept dept;

/** 导出部门单个对象 */
@Excel(name = "部门名称", targetAttr = "deptName", type = Type.EXPORT)
private SysDept dept;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

5、在Controller添加导入方法,updateSupport属性为是否存在则覆盖(可选)

@Log(title = "用户管理", businessType = BusinessType.IMPORT)
@PostMapping("/importData")
public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
{
	ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
	List<SysUser> userList = util.importExcel(file.getInputStream());
	LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest());
	String operName = loginUser.getUsername();
	String message = userService.importUser(userList, updateSupport, operName);
	return AjaxResult.success(message);
}

@GetMapping("/importTemplate")
public AjaxResult importTemplate()
{
	ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
	return util.importTemplateExcel("用户数据");
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

提示

也可以直接到main运行此方法测试。

InputStream is = new FileInputStream(new File("D:\\test.xlsx"));
ExcelUtil<Entity> util = new ExcelUtil<Entity>(Entity.class);
List<Entity> userList = util.importExcel(is);
1
2
3

# 自定义标题信息

参考自定义标题信息

# 自定义数据处理器

参考自定义数据处理器

# 自定义隐藏属性列

参考自定义隐藏属性列

# 导出对象的子列表

参考导出对象的子列表

# 上传下载

首先创建一张上传文件的表,例如:

drop table if exists sys_file_info;
create table sys_file_info (
  file_id           int(11)          not null auto_increment       comment '文件id',
  file_name         varchar(50)      default ''                    comment '文件名称',
  file_path         varchar(255)     default ''                    comment '文件路径',
  primary key (file_id)
) engine=innodb auto_increment=1 default charset=utf8 comment = '文件信息表';
1
2
3
4
5
6
7

# 上传实现流程

1、el-input修改成el-upload

<el-upload
  ref="upload"
  :limit="1"
  accept=".jpg, .png"
  :action="upload.url"
  :headers="upload.headers"
  :file-list="upload.fileList"
  :on-progress="handleFileUploadProgress"
  :on-success="handleFileSuccess"
  :auto-upload="false">
  <el-button slot="trigger" size="small" type="primary">选取文件</el-button>
  <el-button style="margin-left: 10px;" size="small" type="success" :loading="upload.isUploading" @click="submitUpload">上传到服务器</el-button>
  <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

2、引入获取token

import { getToken } from "@/utils/auth";
1

3、data中添加属性

// 上传参数
upload: {
  // 是否禁用上传
  isUploading: false,
  // 设置上传的请求头部
  headers: { Authorization: "Bearer " + getToken() },
  // 上传的地址
  url: process.env.VUE_APP_BASE_API + "/common/upload",
  // 上传的文件列表
  fileList: []
},
1
2
3
4
5
6
7
8
9
10
11

4、新增和修改操作对应处理fileList参数

handleAdd() {
  ...
  this.upload.fileList = [];
}

handleUpdate(row) {
  ...
  this.upload.fileList = [{ name: this.form.fileName, url: this.form.filePath }];
}
1
2
3
4
5
6
7
8
9

5、添加对应事件

// 文件提交处理
submitUpload() {
  this.$refs.upload.submit();
},
// 文件上传中处理
handleFileUploadProgress(event, file, fileList) {
  this.upload.isUploading = true;
},
// 文件上传成功处理
handleFileSuccess(response, file, fileList) {
  this.upload.isUploading = false;
  this.form.filePath = response.url;
  this.msgSuccess(response.msg);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 下载实现流程

1、添加对应按钮和事件

<el-button
  size="mini"
  type="text"
  icon="el-icon-edit"
  @click="handleDownload(scope.row)"
>下载</el-button>
1
2
3
4
5
6

2、实现文件下载

// 文件下载处理
handleDownload(row) {
  var name = row.fileName;
  var url = row.filePath;
  var suffix = url.substring(url.lastIndexOf("."), url.length);
  const a = document.createElement('a')
  a.setAttribute('download', name + suffix)
  a.setAttribute('target', '_blank')
  a.setAttribute('href', url)
  a.click()
}
1
2
3
4
5
6
7
8
9
10
11

# 权限注解

Spring Security提供了Spring EL表达式,允许我们在定义接口访问的方法上面添加注解,来控制访问权限。

# 权限方法

@PreAuthorize注解用于配置接口要求用户拥有某些权限才可访问,它拥有如下方法

方法 参数 描述
hasPermi String 验证用户是否具备某权限
lacksPermi String 验证用户是否不具备某权限,与 hasPermi逻辑相反
hasAnyPermi String 验证用户是否具有以下任意一个权限
hasRole String 判断用户是否拥有某个角色
lacksRole String 验证用户是否不具备某角色,与 isRole逻辑相反
hasAnyRoles String 验证用户是否具有以下任意一个角色,多个逗号分隔

# 使用示例

其中@ss代表的是PermissionService (opens new window)服务,对每个接口拦截并调用PermissionService的对应方法判断接口调用者的权限。

  1. 数据权限示例。
// 符合system:user:list权限要求
@PreAuthorize("@ss.hasPermi('system:user:list')")

// 不符合system:user:list权限要求
@PreAuthorize("@ss.lacksPermi('system:user:list')")

// 符合system:user:add或system:user:edit权限要求即可
@PreAuthorize("@ss.hasAnyPermi('system:user:add,system:user:edit')")
1
2
3
4
5
6
7
8

编程式判断是否有资源权限

if (SecurityUtils.hasPermi("sys:user:edit"))
{
    System.out.println("当前用户有编辑用户权限");
}
1
2
3
4
  1. 角色权限示例。
// 属于user角色
@PreAuthorize("@ss.hasRole('user')")

// 不属于user角色
@PreAuthorize("@ss.lacksRole('user')")

// 属于user或者admin之一
@PreAuthorize("@ss.hasAnyRoles('user,admin')")
1
2
3
4
5
6
7
8

编程式判断是否有角色权限

if (SecurityUtils.hasRole("admin"))
{
    System.out.println("当前用户有admin角色权限");
}
1
2
3
4

权限提示

超级管理员拥有所有权限,不受权限约束。

# 公开接口

如果有些接口是不需要验证权限可以公开访问的,这个时候就需要我们给接口放行。

使用注解方式,只需要在Controller的类或方法上加入@Anonymous该注解即可

// @PreAuthorize("@ss.xxxx('....')") 注释或删除掉原有的权限注解
@Anonymous
@GetMapping("/list")
public List<SysXxxx> list(SysXxxx xxxx)
{
    return xxxxList;
}
1
2
3
4
5
6
7

# 事务管理

参考事务管理实现

# 异常处理

参考异常处理实现

# 参数验证

参考参数验证

# 数据脱敏

参考数据脱敏

# 系统日志

参考系统日志实现

# 数据权限

参考数据权限实现

# 多数据源

参考多数据源实现

# 代码生成

参考代码生成实现

# 定时任务

参考定时任务实现

# 系统接口

参考系统接口实现

# 防重复提交

防重复提交实现

# 国际化支持

# 后台国际化流程

前置参考 后台国际化流程

SysLoginController.java新增修改语言方法

@GetMapping("/changeLanguage")
public AjaxResult changeLanguage(String lang)
{
	return AjaxResult.success();
}
1
2
3
4
5

SecurityConfig.java允许匿名访问此方法

.antMatchers("/changeLanguage").permitAll()
1

# 前端国际化流程

1、package.jsondependencies节点添加vue-i18n

"vue-i18n": "7.3.2",
1

2、src目录下创建lang目录,存放国际化文件
此处包含三个文件,分别是 index.js zh.js en.js

// index.js
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import Cookies from 'js-cookie'
import elementEnLocale from 'element-ui/lib/locale/lang/en' // element-ui lang
import elementZhLocale from 'element-ui/lib/locale/lang/zh-CN'// element-ui lang
import enLocale from './en'
import zhLocale from './zh'

Vue.use(VueI18n)

const messages = {
  en_US: {
    ...enLocale,
    ...elementEnLocale
  },
  zh_CN: {
    ...zhLocale,
    ...elementZhLocale
  }
}

const i18n = new VueI18n({
  // 设置语言 选项 en | zh
  locale: Cookies.get('language') || 'zh_CN',
  // 设置文本内容
  messages
})

export default i18n
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// zh.js
export default {
  login: {
    title: '若依后台管理系统',
    logIn: '登录',
    username: '账号',
    password: '密码',
	code: '验证码',
    rememberMe: '记住密码'
  },
  tagsView: {
    refresh: '刷新',
    close: '关闭',
    closeOthers: '关闭其它',
    closeAll: '关闭所有'
  },
  settings: {
    title: '系统布局配置',
    theme: '主题色',
    tagsView: '开启 Tags-View',
    fixedHeader: '固定 Header',
    sidebarLogo: '侧边栏 Logo'
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// en.js
export default {
  login: {
    title: 'RuoYi Login Form',
    logIn: 'Login in',
    username: 'Username',
    password: 'Password',
	code: 'Code',
    rememberMe: 'Remember Me'
  },
  tagsView: {
    refresh: 'Refresh',
    close: 'Close',
    closeOthers: 'Close Others',
    closeAll: 'Close All'
  },
  settings: {
    title: 'Page style setting',
    theme: 'Theme Color',
    tagsView: 'Open Tags-View',
    fixedHeader: 'Fixed Header',
    sidebarLogo: 'Sidebar Logo'
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

3、在src/main.js中增量添加i18n

 


 










 



import i18n from './lang'

Vue.use(Element, {
  i18n: (key, value) => i18n.t(key, value),
  size: Cookies.get('size') || 'medium'
})


Vue.config.productionTip = false

new Vue({
  el: '#app',
  router,
  store,
  i18n,
  render: h => h(App)
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

4、在src/store/getters.js中添加language

language: state => state.app.language,
1

5、在src/store/modules/app.js中增量添加i18n

const state = {
  language: Cookies.get('language') || 'en'
}

const mutations = {
  SET_LANGUAGE: (state, language) => {
    state.language = language
    Cookies.set('language', language)
  }
}

const actions = {
  setLanguage({ commit }, language) {
    commit('SET_LANGUAGE', language)
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

6、在src/components/LangSelect/index.vue中创建汉化组件

<template>
  <el-dropdown trigger="click" class="international" @command="handleSetLanguage">
    <div>
      <svg-icon class-name="international-icon" icon-class="language" />
    </div>
    <el-dropdown-menu slot="dropdown">
      <el-dropdown-item :disabled="language==='zh_CN'" command="zh_CN">
        中文
      </el-dropdown-item>
      <el-dropdown-item :disabled="language==='en_US'" command="en_US">
        English
      </el-dropdown-item>
    </el-dropdown-menu>
  </el-dropdown>
</template>

<script>
import { changeLanguage } from "@/api/login";


export default {
  computed: {
    language() {
      return this.$store.getters.language
    }
  },
  methods: {
    handleSetLanguage(value) {
      this.$i18n.locale = value
      this.$store.dispatch('app/setLanguage', value)
      this.$message({ message: '设置语言成功', type: 'success' })
      changeLanguage(value).then(response => {
        window.location.reload();
      });
    }
  }
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

7、在login.js新增修改语言方法

// 修改语言
export function changeLanguage(lang){
  return request({
    url: '/changeLanguage',
    method: 'get',
    headers: {
      isToken: false,
    },
    params: {
      lang: lang
    }
  })
}
1
2
3
4
5
6
7
8
9
10
11
12
13

8、登录页面汉化

<template>
  <div class="login">
    <el-form ref="loginForm" :model="loginForm" :rules="loginRules" class="login-form">
      <h3 class="title">{{ $t('login.title') }}</h3>
      <lang-select class="set-language" />
      <el-form-item prop="username">
        <el-input
          v-model="loginForm.username"
          type="text"
          auto-complete="off"
          :placeholder="$t('login.username')"
        >
          <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon" />
        </el-input>
      </el-form-item>
      <el-form-item prop="password">
        <el-input
          v-model="loginForm.password"
          type="password"
          auto-complete="off"
          :placeholder="$t('login.password')"
          @keyup.enter.native="handleLogin"
        >
          <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon" />
        </el-input>
      </el-form-item>
      <el-form-item prop="code" v-if="captchaEnabled">
        <el-input
          v-model="loginForm.code"
          auto-complete="off"
          :placeholder="$t('login.code')"
          style="width: 63%"
          @keyup.enter.native="handleLogin"
        >
          <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon" />
        </el-input>
        <div class="login-code">
          <img :src="codeUrl" @click="getCode" class="login-code-img"/>
        </div>
      </el-form-item>
      <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">{{ $t('login.rememberMe') }}</el-checkbox>
      <el-form-item style="width:100%;">
        <el-button
          :loading="loading"
          size="medium"
          type="primary"
          style="width:100%;"
          @click.native.prevent="handleLogin"
        >
          <span v-if="!loading">{{ $t('login.logIn') }}</span>
          <span v-else>登 录 中...</span>
        </el-button>
        <div style="float: right;" v-if="register">
          <router-link class="link-type" :to="'/register'">立即注册</router-link>
        </div>
      </el-form-item>
    </el-form>
    <!--  底部  -->
    <div class="el-login-footer">
      <span>Copyright © 2018-2024 ruoyi.vip All Rights Reserved.</span>
    </div>
  </div>
</template>

<script>
import LangSelect from '@/components/LangSelect'
import { getCodeImg } from "@/api/login";
import Cookies from "js-cookie";
import { encrypt, decrypt } from '@/utils/jsencrypt'

export default {
  name: "Login",
  components: { LangSelect },
  data() {
    return {
      codeUrl: "",
      loginForm: {
        username: "admin",
        password: "admin123",
        rememberMe: false,
        code: "",
        uuid: ""
      },
      loginRules: {
        username: [
          { required: true, trigger: "blur", message: "请输入您的账号" }
        ],
        password: [
          { required: true, trigger: "blur", message: "请输入您的密码" }
        ],
        code: [{ required: true, trigger: "change", message: "请输入验证码" }]
      },
      loading: false,
      // 验证码开关
      captchaEnabled: true,
      // 注册开关
      register: false,
      redirect: undefined
    };
  },
  watch: {
    $route: {
      handler: function(route) {
        this.redirect = route.query && route.query.redirect;
      },
      immediate: true
    }
  },
  created() {
    this.getCode();
    this.getCookie();
  },
  methods: {
    getCode() {
      getCodeImg().then(res => {
        this.captchaEnabled = res.captchaEnabled === undefined ? true : res.captchaEnabled;
        if (this.captchaEnabled) {
          this.codeUrl = "data:image/gif;base64," + res.img;
          this.loginForm.uuid = res.uuid;
        }
      });
    },
    getCookie() {
      const username = Cookies.get("username");
      const password = Cookies.get("password");
      const rememberMe = Cookies.get('rememberMe')
      this.loginForm = {
        username: username === undefined ? this.loginForm.username : username,
        password: password === undefined ? this.loginForm.password : decrypt(password),
        rememberMe: rememberMe === undefined ? false : Boolean(rememberMe)
      };
    },
    handleLogin() {
      this.$refs.loginForm.validate(valid => {
        if (valid) {
          this.loading = true;
          if (this.loginForm.rememberMe) {
            Cookies.set("username", this.loginForm.username, { expires: 30 });
            Cookies.set("password", encrypt(this.loginForm.password), { expires: 30 });
            Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 });
          } else {
            Cookies.remove("username");
            Cookies.remove("password");
            Cookies.remove('rememberMe');
          }
          this.$store.dispatch("Login", this.loginForm).then(() => {
            this.$router.push({ path: this.redirect || "/" }).catch(()=>{});
          }).catch(() => {
            this.loading = false;
            if (this.captchaEnabled) {
              this.getCode();
            }
          });
        }
      });
    }
  }
};
</script>

<style rel="stylesheet/scss" lang="scss">
.login {
  display: flex;
  justify-content: center;
  align-items: center;
  height: 100%;
  background-image: url("../assets/images/login-background.jpg");
  background-size: cover;
}
.title {
  margin: 0px auto 30px auto;
  text-align: center;
  color: #707070;
}

.login-form {
  border-radius: 6px;
  background: #ffffff;
  width: 400px;
  padding: 25px 25px 5px 25px;
  .el-input {
    height: 38px;
    input {
      height: 38px;
    }
  }
  .input-icon {
    height: 39px;
    width: 14px;
    margin-left: 2px;
  }
}
.login-tip {
  font-size: 13px;
  text-align: center;
  color: #bfbfbf;
}
.login-code {
  width: 33%;
  height: 38px;
  float: right;
  img {
    cursor: pointer;
    vertical-align: middle;
  }
}
.el-login-footer {
  height: 40px;
  line-height: 40px;
  position: fixed;
  bottom: 0;
  width: 100%;
  text-align: center;
  color: #fff;
  font-family: Arial;
  font-size: 12px;
  letter-spacing: 1px;
}
.login-code-img {
  height: 38px;
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
普通文本使用方式: {{ $t('login.title') }}
标签内使用方式:   :placeholder="$t('login.password')"
js内使用方式       this.$t('login.user.password.not.match')
1
2
3

# 新建子模块

参考新建子模块