diff --git a/src/assets/icons/svg/documentation.svg b/src/assets/icons/svg/documentation.svg
new file mode 100644
index 0000000..ae893ac
--- /dev/null
+++ b/src/assets/icons/svg/documentation.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/assets/icons/svg/download.svg b/src/assets/icons/svg/download.svg
new file mode 100644
index 0000000..b2d79f7
--- /dev/null
+++ b/src/assets/icons/svg/download.svg
@@ -0,0 +1,14 @@
+
+
diff --git a/src/assets/icons/svg/drag.svg b/src/assets/icons/svg/drag.svg
new file mode 100644
index 0000000..3e822c5
--- /dev/null
+++ b/src/assets/icons/svg/drag.svg
@@ -0,0 +1,4 @@
+
diff --git a/src/directive/dialog/drag.js b/src/directive/dialog/drag.js
new file mode 100644
index 0000000..e754a40
--- /dev/null
+++ b/src/directive/dialog/drag.js
@@ -0,0 +1,65 @@
+/**
+ * v-dialogDrag 弹窗拖拽
+ * Copyright (c) 2019 muyu
+ */
+
+export default {
+ bind(el, binding, vnode, oldVnode) {
+ const value = binding.value
+ if (value == false) return
+ // 获取拖拽内容头部
+ const dialogHeaderEl = el.querySelector('.el-dialog__header');
+ const dragDom = el.querySelector('.el-dialog');
+ dialogHeaderEl.style.cursor = 'move';
+ // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
+ const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null);
+ dragDom.style.position = 'absolute';
+ dragDom.style.marginTop = 0;
+ let width = dragDom.style.width;
+ if (width.includes('%')) {
+ width = +document.body.clientWidth * (+width.replace(/\%/g, '') / 100);
+ } else {
+ width = +width.replace(/\px/g, '');
+ }
+ dragDom.style.left = `${(document.body.clientWidth - width) / 2}px`;
+ // 鼠标按下事件
+ dialogHeaderEl.onmousedown = (e) => {
+ // 鼠标按下,计算当前元素距离可视区的距离 (鼠标点击位置距离可视窗口的距离)
+ const disX = e.clientX - dialogHeaderEl.offsetLeft;
+ const disY = e.clientY - dialogHeaderEl.offsetTop;
+
+ // 获取到的值带px 正则匹配替换
+ let styL, styT;
+
+ // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
+ if (sty.left.includes('%')) {
+ styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100);
+ styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100);
+ } else {
+ styL = +sty.left.replace(/\px/g, '');
+ styT = +sty.top.replace(/\px/g, '');
+ }
+
+
+ // 鼠标拖拽事件
+ document.onmousemove = function (e) {
+ // 通过事件委托,计算移动的距离 (开始拖拽至结束拖拽的距离)
+ const l = e.clientX - disX;
+ const t = e.clientY - disY;
+
+ let finallyL = l + styL
+ let finallyT = t + styT
+
+ // 移动当前元素
+ dragDom.style.left = `${finallyL}px`;
+ dragDom.style.top = `${finallyT}px`;
+
+ };
+
+ document.onmouseup = function (e) {
+ document.onmousemove = null;
+ document.onmouseup = null;
+ };
+ }
+ }
+};
diff --git a/src/plugins/download.js b/src/plugins/download.js
new file mode 100644
index 0000000..8033b0d
--- /dev/null
+++ b/src/plugins/download.js
@@ -0,0 +1,38 @@
+import axios from 'axios'
+import {Message} from 'element-ui'
+import {saveAs} from 'file-saver'
+import {getToken} from '@/utils/auth'
+import errorCode from '@/utils/errorCode'
+import {blobValidate} from "@/utils/muyu";
+
+const baseURL = process.env.VUE_APP_BASE_API
+
+export default {
+ zip(url, name) {
+ var url = baseURL + url
+ axios({
+ method: 'get',
+ url: url,
+ responseType: 'blob',
+ headers: {'Authorization': 'Bearer ' + getToken()}
+ }).then((res) => {
+ const isBlob = blobValidate(res.data);
+ if (isBlob) {
+ const blob = new Blob([res.data], {type: 'application/zip'})
+ this.saveAs(blob, name)
+ } else {
+ this.printErrMsg(res.data);
+ }
+ })
+ },
+ saveAs(text, name, opts) {
+ saveAs(text, name, opts);
+ },
+ async printErrMsg(data) {
+ const resText = await data.text();
+ const rspObj = JSON.parse(resText);
+ const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default']
+ Message.error(errMsg);
+ }
+}
+
diff --git a/src/utils/dict/DictOptions.js b/src/utils/dict/DictOptions.js
new file mode 100644
index 0000000..9a3ddcb
--- /dev/null
+++ b/src/utils/dict/DictOptions.js
@@ -0,0 +1,51 @@
+import {mergeRecursive} from "@/utils/muyu";
+import dictConverter from './DictConverter'
+
+export const options = {
+ metas: {
+ '*': {
+ /**
+ * 字典请求,方法签名为function(dictMeta: DictMeta): Promise
+ */
+ request: (dictMeta) => {
+ console.log(`load dict ${dictMeta.type}`)
+ return Promise.resolve([])
+ },
+ /**
+ * 字典响应数据转换器,方法签名为function(response: Object, dictMeta: DictMeta): DictData
+ */
+ responseConverter,
+ labelField: 'label',
+ valueField: 'value',
+ },
+ },
+ /**
+ * 默认标签字段
+ */
+ DEFAULT_LABEL_FIELDS: ['label', 'name', 'title'],
+ /**
+ * 默认值字段
+ */
+ DEFAULT_VALUE_FIELDS: ['value', 'id', 'uid', 'key'],
+}
+
+/**
+ * 映射字典
+ * @param {Object} response 字典数据
+ * @param {DictMeta} dictMeta 字典元数据
+ * @returns {DictData}
+ */
+function responseConverter(response, dictMeta) {
+ const dicts = response.content instanceof Array ? response.content : response
+ if (dicts === undefined) {
+ console.warn(`no dict data of "${dictMeta.type}" found in the response`)
+ return []
+ }
+ return dicts.map(d => dictConverter(d, dictMeta))
+}
+
+export function mergeOptions(src) {
+ mergeRecursive(options, src)
+}
+
+export default options