diff --git a/docker/deploy.sh b/docker/deploy.sh
new file mode 100644
index 0000000..5b7a1d5
--- /dev/null
+++ b/docker/deploy.sh
@@ -0,0 +1,67 @@
+#!/bin/sh
+
+# 使用说明,用来提示输入参数
+usage() {
+ echo "Usage: sh 执行脚本.sh [port|base|modules|stop|rm]"
+ exit 1
+}
+
+# 开启所需端口
+port(){
+ firewall-cmd --add-port=80/tcp --permanent
+ firewall-cmd --add-port=8080/tcp --permanent
+ firewall-cmd --add-port=8848/tcp --permanent
+ firewall-cmd --add-port=9848/tcp --permanent
+ firewall-cmd --add-port=9849/tcp --permanent
+ firewall-cmd --add-port=6379/tcp --permanent
+ firewall-cmd --add-port=3306/tcp --permanent
+ firewall-cmd --add-port=9100/tcp --permanent
+ firewall-cmd --add-port=9200/tcp --permanent
+ firewall-cmd --add-port=9201/tcp --permanent
+ firewall-cmd --add-port=9202/tcp --permanent
+ firewall-cmd --add-port=9203/tcp --permanent
+ firewall-cmd --add-port=9300/tcp --permanent
+ service firewalld restart
+}
+
+# 启动基础环境(必须)
+base(){
+ docker-compose up -d jing-mysql jing-redis jing-nacos
+}
+
+# 启动程序模块(必须)
+modules(){
+ docker-compose up -d jing-nginx jing-gateway jing-auth jing-modules-system
+}
+
+# 关闭所有环境/模块
+stop(){
+ docker-compose stop
+}
+
+# 删除所有环境/模块
+rm(){
+ docker-compose rm
+}
+
+# 根据输入参数,选择执行对应方法,不输入则执行使用说明
+case "$1" in
+"port")
+ port
+;;
+"base")
+ base
+;;
+"modules")
+ modules
+;;
+"stop")
+ stop
+;;
+"rm")
+ rm
+;;
+*)
+ usage
+;;
+esac
diff --git a/jing-common/jing-common-core/src/main/java/com/jing/common/core/exception/DemoModeException.java b/jing-common/jing-common-core/src/main/java/com/jing/common/core/exception/DemoModeException.java
new file mode 100644
index 0000000..e9a4ddf
--- /dev/null
+++ b/jing-common/jing-common-core/src/main/java/com/jing/common/core/exception/DemoModeException.java
@@ -0,0 +1,15 @@
+package com.jing.common.core.exception;
+
+/**
+ * 演示模式异常
+ *
+ * @author ruoyi
+ */
+public class DemoModeException extends RuntimeException
+{
+ private static final long serialVersionUID = 1L;
+
+ public DemoModeException()
+ {
+ }
+}
diff --git a/jing-common/jing-common-core/src/main/java/com/jing/common/core/utils/DateUtils.java b/jing-common/jing-common-core/src/main/java/com/jing/common/core/utils/DateUtils.java
new file mode 100644
index 0000000..d6ae9fa
--- /dev/null
+++ b/jing-common/jing-common-core/src/main/java/com/jing/common/core/utils/DateUtils.java
@@ -0,0 +1,183 @@
+package com.jing.common.core.utils;
+
+import java.lang.management.ManagementFactory;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Date;
+import org.apache.commons.lang3.time.DateFormatUtils;
+
+/**
+ * 时间工具类
+ *
+ * @author ruoyi
+ */
+public class DateUtils extends org.apache.commons.lang3.time.DateUtils
+{
+ public static String YYYY = "yyyy";
+
+ public static String YYYY_MM = "yyyy-MM";
+
+ public static String YYYY_MM_DD = "yyyy-MM-dd";
+
+ public static String YYYYMMDDHHMMSS = "yyyyMMddHHmmss";
+
+ public static String YYYY_MM_DD_HH_MM_SS = "yyyy-MM-dd HH:mm:ss";
+
+ private static String[] parsePatterns = {
+ "yyyy-MM-dd", "yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy-MM",
+ "yyyy/MM/dd", "yyyy/MM/dd HH:mm:ss", "yyyy/MM/dd HH:mm", "yyyy/MM",
+ "yyyy.MM.dd", "yyyy.MM.dd HH:mm:ss", "yyyy.MM.dd HH:mm", "yyyy.MM"};
+
+ /**
+ * 获取当前Date型日期
+ *
+ * @return Date() 当前日期
+ */
+ public static Date getNowDate()
+ {
+ return new Date();
+ }
+
+ /**
+ * 获取当前日期, 默认格式为yyyy-MM-dd
+ *
+ * @return String
+ */
+ public static String getDate()
+ {
+ return dateTimeNow(YYYY_MM_DD);
+ }
+
+ public static final String getTime()
+ {
+ return dateTimeNow(YYYY_MM_DD_HH_MM_SS);
+ }
+
+ public static final String dateTimeNow()
+ {
+ return dateTimeNow(YYYYMMDDHHMMSS);
+ }
+
+ public static final String dateTimeNow(final String format)
+ {
+ return parseDateToStr(format, new Date());
+ }
+
+ public static final String dateTime(final Date date)
+ {
+ return parseDateToStr(YYYY_MM_DD, date);
+ }
+
+ public static final String parseDateToStr(final String format, final Date date)
+ {
+ return new SimpleDateFormat(format).format(date);
+ }
+
+ public static final Date dateTime(final String format, final String ts)
+ {
+ try
+ {
+ return new SimpleDateFormat(format).parse(ts);
+ }
+ catch (ParseException e)
+ {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * 日期路径 即年/月/日 如2018/08/08
+ */
+ public static final String datePath()
+ {
+ Date now = new Date();
+ return DateFormatUtils.format(now, "yyyy/MM/dd");
+ }
+
+ /**
+ * 日期路径 即年/月/日 如20180808
+ */
+ public static final String dateTime()
+ {
+ Date now = new Date();
+ return DateFormatUtils.format(now, "yyyyMMdd");
+ }
+
+ /**
+ * 日期型字符串转化为日期 格式
+ */
+ public static Date parseDate(Object str)
+ {
+ if (str == null)
+ {
+ return null;
+ }
+ try
+ {
+ return parseDate(str.toString(), parsePatterns);
+ }
+ catch (ParseException e)
+ {
+ return null;
+ }
+ }
+
+ /**
+ * 获取服务器启动时间
+ */
+ public static Date getServerStartDate()
+ {
+ long time = ManagementFactory.getRuntimeMXBean().getStartTime();
+ return new Date(time);
+ }
+
+ /**
+ * 计算时间差
+ *
+ * @param endDate 最后时间
+ * @param startTime 开始时间
+ * @return 时间差(天/小时/分钟)
+ */
+ public static String timeDistance(Date endDate, Date startTime)
+ {
+ long nd = 1000 * 24 * 60 * 60;
+ long nh = 1000 * 60 * 60;
+ long nm = 1000 * 60;
+ // long ns = 1000;
+ // 获得两个时间的毫秒时间差异
+ long diff = endDate.getTime() - startTime.getTime();
+ // 计算差多少天
+ long day = diff / nd;
+ // 计算差多少小时
+ long hour = diff % nd / nh;
+ // 计算差多少分钟
+ long min = diff % nd % nh / nm;
+ // 计算差多少秒//输出结果
+ // long sec = diff % nd % nh % nm / ns;
+ return day + "天" + hour + "小时" + min + "分钟";
+ }
+
+ /**
+ * 增加 LocalDateTime ==> Date
+ */
+ public static Date toDate(LocalDateTime temporalAccessor)
+ {
+ ZonedDateTime zdt = temporalAccessor.atZone(ZoneId.systemDefault());
+ return Date.from(zdt.toInstant());
+ }
+
+ /**
+ * 增加 LocalDate ==> Date
+ */
+ public static Date toDate(LocalDate temporalAccessor)
+ {
+ LocalDateTime localDateTime = LocalDateTime.of(temporalAccessor, LocalTime.of(0, 0, 0));
+ ZonedDateTime zdt = localDateTime.atZone(ZoneId.systemDefault());
+ return Date.from(zdt.toInstant());
+ }
+}
diff --git a/jing-ui/src/api/system/dept.js b/jing-ui/src/api/system/dept.js
new file mode 100644
index 0000000..fc943cd
--- /dev/null
+++ b/jing-ui/src/api/system/dept.js
@@ -0,0 +1,52 @@
+import request from '@/utils/request'
+
+// 查询部门列表
+export function listDept(query) {
+ return request({
+ url: '/system/dept/list',
+ method: 'get',
+ params: query
+ })
+}
+
+// 查询部门列表(排除节点)
+export function listDeptExcludeChild(deptId) {
+ return request({
+ url: '/system/dept/list/exclude/' + deptId,
+ method: 'get'
+ })
+}
+
+// 查询部门详细
+export function getDept(deptId) {
+ return request({
+ url: '/system/dept/' + deptId,
+ method: 'get'
+ })
+}
+
+// 新增部门
+export function addDept(data) {
+ return request({
+ url: '/system/dept',
+ method: 'post',
+ data: data
+ })
+}
+
+// 修改部门
+export function updateDept(data) {
+ return request({
+ url: '/system/dept',
+ method: 'put',
+ data: data
+ })
+}
+
+// 删除部门
+export function delDept(deptId) {
+ return request({
+ url: '/system/dept/' + deptId,
+ method: 'delete'
+ })
+}
\ No newline at end of file
diff --git a/jing-ui/src/assets/icons/svg/date-range.svg b/jing-ui/src/assets/icons/svg/date-range.svg
new file mode 100644
index 0000000..fda571e
--- /dev/null
+++ b/jing-ui/src/assets/icons/svg/date-range.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/jing-ui/src/assets/icons/svg/date.svg b/jing-ui/src/assets/icons/svg/date.svg
new file mode 100644
index 0000000..52dc73e
--- /dev/null
+++ b/jing-ui/src/assets/icons/svg/date.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/jing-ui/src/assets/icons/svg/dict.svg b/jing-ui/src/assets/icons/svg/dict.svg
new file mode 100644
index 0000000..4849377
--- /dev/null
+++ b/jing-ui/src/assets/icons/svg/dict.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/jing-ui/src/components/Crontab/day.vue b/jing-ui/src/components/Crontab/day.vue
new file mode 100644
index 0000000..fe3eaf0
--- /dev/null
+++ b/jing-ui/src/components/Crontab/day.vue
@@ -0,0 +1,161 @@
+
+
+
+
+ 日,允许的通配符[, - * ? / L W]
+
+
+
+
+
+ 不指定
+
+
+
+
+
+ 周期从
+ -
+ 日
+
+
+
+
+
+ 从
+ 号开始,每
+ 日执行一次
+
+
+
+
+
+ 每月
+ 号最近的那个工作日
+
+
+
+
+
+ 本月最后一天
+
+
+
+
+
+ 指定
+
+ {{item}}
+
+
+
+
+
+
+
diff --git a/jing-ui/src/store/modules/dict.js b/jing-ui/src/store/modules/dict.js
new file mode 100644
index 0000000..7a1b2f0
--- /dev/null
+++ b/jing-ui/src/store/modules/dict.js
@@ -0,0 +1,50 @@
+const state = {
+ dict: new Array()
+}
+const mutations = {
+ SET_DICT: (state, { key, value }) => {
+ if (key !== null && key !== "") {
+ state.dict.push({
+ key: key,
+ value: value
+ })
+ }
+ },
+ REMOVE_DICT: (state, key) => {
+ try {
+ for (let i = 0; i < state.dict.length; i++) {
+ if (state.dict[i].key == key) {
+ state.dict.splice(i, 1)
+ return true
+ }
+ }
+ } catch (e) {
+ }
+ },
+ CLEAN_DICT: (state) => {
+ state.dict = new Array()
+ }
+}
+
+const actions = {
+ // 设置字典
+ setDict({ commit }, data) {
+ commit('SET_DICT', data)
+ },
+ // 删除字典
+ removeDict({ commit }, key) {
+ commit('REMOVE_DICT', key)
+ },
+ // 清空字典
+ cleanDict({ commit }) {
+ commit('CLEAN_DICT')
+ }
+}
+
+export default {
+ namespaced: true,
+ state,
+ mutations,
+ actions
+}
+
diff --git a/jing-ui/src/utils/dict/Dict.js b/jing-ui/src/utils/dict/Dict.js
new file mode 100644
index 0000000..104bd6e
--- /dev/null
+++ b/jing-ui/src/utils/dict/Dict.js
@@ -0,0 +1,82 @@
+import Vue from 'vue'
+import { mergeRecursive } from "@/utils/ruoyi";
+import DictMeta from './DictMeta'
+import DictData from './DictData'
+
+const DEFAULT_DICT_OPTIONS = {
+ types: [],
+}
+
+/**
+ * @classdesc 字典
+ * @property {Object} label 标签对象,内部属性名为字典类型名称
+ * @property {Object} dict 字段数组,内部属性名为字典类型名称
+ * @property {Array.} _dictMetas 字典元数据数组
+ */
+export default class Dict {
+ constructor() {
+ this.owner = null
+ this.label = {}
+ this.type = {}
+ }
+
+ init(options) {
+ if (options instanceof Array) {
+ options = { types: options }
+ }
+ const opts = mergeRecursive(DEFAULT_DICT_OPTIONS, options)
+ if (opts.types === undefined) {
+ throw new Error('need dict types')
+ }
+ const ps = []
+ this._dictMetas = opts.types.map(t => DictMeta.parse(t))
+ this._dictMetas.forEach(dictMeta => {
+ const type = dictMeta.type
+ Vue.set(this.label, type, {})
+ Vue.set(this.type, type, [])
+ if (dictMeta.lazy) {
+ return
+ }
+ ps.push(loadDict(this, dictMeta))
+ })
+ return Promise.all(ps)
+ }
+
+ /**
+ * 重新加载字典
+ * @param {String} type 字典类型
+ */
+ reloadDict(type) {
+ const dictMeta = this._dictMetas.find(e => e.type === type)
+ if (dictMeta === undefined) {
+ return Promise.reject(`the dict meta of ${type} was not found`)
+ }
+ return loadDict(this, dictMeta)
+ }
+}
+
+/**
+ * 加载字典
+ * @param {Dict} dict 字典
+ * @param {DictMeta} dictMeta 字典元数据
+ * @returns {Promise}
+ */
+function loadDict(dict, dictMeta) {
+ return dictMeta.request(dictMeta)
+ .then(response => {
+ const type = dictMeta.type
+ let dicts = dictMeta.responseConverter(response, dictMeta)
+ if (!(dicts instanceof Array)) {
+ console.error('the return of responseConverter must be Array.')
+ dicts = []
+ } else if (dicts.filter(d => d instanceof DictData).length !== dicts.length) {
+ console.error('the type of elements in dicts must be DictData')
+ dicts = []
+ }
+ dict.type[type].splice(0, Number.MAX_SAFE_INTEGER, ...dicts)
+ dicts.forEach(d => {
+ Vue.set(dict.label[type], d.value, d.label)
+ })
+ return dicts
+ })
+}
diff --git a/jing-ui/src/utils/dict/DictConverter.js b/jing-ui/src/utils/dict/DictConverter.js
new file mode 100644
index 0000000..0cf5df8
--- /dev/null
+++ b/jing-ui/src/utils/dict/DictConverter.js
@@ -0,0 +1,17 @@
+import DictOptions from './DictOptions'
+import DictData from './DictData'
+
+export default function(dict, dictMeta) {
+ const label = determineDictField(dict, dictMeta.labelField, ...DictOptions.DEFAULT_LABEL_FIELDS)
+ const value = determineDictField(dict, dictMeta.valueField, ...DictOptions.DEFAULT_VALUE_FIELDS)
+ return new DictData(dict[label], dict[value], dict)
+}
+
+/**
+ * 确定字典字段
+ * @param {DictData} dict
+ * @param {...String} fields
+ */
+function determineDictField(dict, ...fields) {
+ return fields.find(f => Object.prototype.hasOwnProperty.call(dict, f))
+}