@@ -1,5 +0,0 @@ | |||
language: node_js | |||
node_js: stable | |||
script: npm run lint | |||
notifications: | |||
email: false |
@@ -1,30 +1,4 @@ | |||
<p align="center"> | |||
<a href="https://www.iviewui.com"> | |||
<img width="200" src="https://file.iviewui.com/logo-new.svg"> | |||
</a> | |||
</p> | |||
# iView Admin | |||
[](https://github.com/iview/iview-admin/releases) | |||
[](https://travis-ci.org/iview/iview-admin) | |||
[](https://github.com/vuejs/vue) | |||
[](https://github.com/iview/iview) | |||
[]() | |||
[更新日志](https://github.com/iview/iview-admin/releases) | |||
[使用文档](https://lison16.github.io/iview-admin-doc/#/) | |||
[在线访问](https://admin.iviewui.com/) | |||
[简化版模板](https://github.com/iview/iview-admin/tree/template) | |||
[教学视频(26课时)](https://segmentfault.com/ls/1650000016221751?utm_source=banner) | |||
`注:在线版本会在开发版本新小版本发布后更新到相应版本,所以如果想体验最新版本iview-admin,请clone完整项目代码到本地运行。` | |||
## Install | |||
# Install | |||
```bush | |||
// install dependencies | |||
npm install | |||
@@ -37,151 +11,4 @@ npm run dev | |||
### Production(Build) | |||
```bush | |||
npm run build | |||
``` | |||
## 简介 | |||
  iView admin是基于Vue.js,搭配使用[iView](https://www.iviewui.com) UI组件库形成的一套后台集成解决方案,由TalkingData前端可视化团队部分成员开发维护。iView admin遵守iView设计和开发约定,风格统一,设计考究,并且更多功能在不停开发中。 | |||
如果您想查看iview-admin的更新动态,您可以到[更新日志](https://github.com/iview/iview-admin/releases)查看了解最新更新;如果您是新手,想快速入手iview-admin,您可以到[使用教程](https://github.com/iview/iview-admin/wiki)查看讲解;如果您想在线体验iview-admin,您可以到[在线访问](https://admin.iviewui.com/)体验。如果你只是想要一个清醒爽朗的界面,那你可以下载[简化版模板](https://github.com/iview/iview-admin/tree/template)来做开发。 | |||
## 功能 | |||
- 登录/登出 | |||
- 权限管理 | |||
- 列表过滤 | |||
- 权限切换 | |||
- 多语言切换 | |||
- 组件 | |||
- 富文本编辑器 | |||
- Markdown编辑器 | |||
- 城市级联 | |||
- 图片预览编辑 | |||
- 可拖拽列表 | |||
- 文件上传 | |||
- 数字渐变 | |||
- split-pane | |||
- 表单编辑 | |||
- 文章发布 | |||
- 工作流 | |||
- 表格 | |||
- 可拖拽排序 | |||
- 可编辑表格 | |||
- 行内编辑 | |||
- 单元格编辑 | |||
- 可搜索表格 | |||
- 表格导出数据 | |||
- 导出为Csv文件 | |||
- 导出为Xls文件 | |||
- 表格转图片 | |||
- 错误页面 | |||
- 403页面 | |||
- 404页面 | |||
- 500页面 | |||
- 高级路由 | |||
- 动态路由 | |||
- 带参页面 | |||
- 换肤 | |||
- 收缩侧边栏 | |||
- tag标签导航 | |||
- 面包屑导航 | |||
- 全屏/退出全屏 | |||
- 锁屏 | |||
- 消息中心 | |||
- 个人中心 | |||
## 文件结构 | |||
```shell | |||
. | |||
├── build 项目构建配置 | |||
├── config 开发相关配置 | |||
├── public 打包所需静态资源 | |||
└── src | |||
├── api AJAX请求 | |||
└── assets 项目静态资源 | |||
├── icons 自定义图标资源 | |||
└── images 图片资源 | |||
├── components 业务组件 | |||
├── config 项目运行配置 | |||
├── directive 自定义指令 | |||
├── libs 封装工具函数 | |||
├── locale 多语言文件 | |||
├── mock mock模拟数据 | |||
├── router 路由配置 | |||
├── store Vuex配置 | |||
├── view 页面文件 | |||
└── tests 测试相关 | |||
``` | |||
## Links | |||
- [TalkingData](https://github.com/TalkingData) | |||
- [iView](https://github.com/iview/iview) | |||
- [Vue](https://github.com/vuejs/vue) | |||
- [Webpack](https://github.com/webpack/webpack) | |||
## 效果展示 | |||
- 响应式布局首页 | |||
 | |||
- 标签导航 | |||
 | |||
- 权限管理 | |||
 | |||
- 可拖拽列表 | |||
 | |||
- 图片预览编辑 | |||
 | |||
- 文件上传 | |||
 | |||
- 数字渐变 | |||
 | |||
- split-pane | |||
 | |||
- 文章发布 | |||
 | |||
- 工作流 | |||
 | |||
- 可拖拽表格 | |||
 | |||
- 可编辑表格 | |||
 | |||
- 表格导出数据 | |||
 | |||
- 表格转图片 | |||
 | |||
- 错误页面 | |||
 | |||
- 锁屏 | |||
 | |||
- 可收缩侧边栏 | |||
 | |||
- 主题切换 | |||
 | |||
- 消息中心 | |||
 | |||
### 💖💖 If you find this project helpful, maybe you can buy me a coffee. 💖💖 | |||
 | |||
## License | |||
[MIT](http://opensource.org/licenses/MIT) | |||
Copyright (c) 2016-present, iView | |||
``` |
@@ -0,0 +1,3 @@ | |||
{ | |||
"pluginsFile": "tests/e2e/plugins/index.js" | |||
} |
@@ -0,0 +1,69 @@ | |||
{ | |||
"name": "iview-admin", | |||
"version": "2.0.0", | |||
"author": "Lison<lison.modern@gmail.com>", | |||
"private": false, | |||
"scripts": { | |||
"dev": "vue-cli-service serve --open", | |||
"build": "vue-cli-service build", | |||
"lint": "vue-cli-service lint", | |||
"test:unit": "vue-cli-service test:unit", | |||
"test:e2e": "vue-cli-service test:e2e" | |||
}, | |||
"dependencies": { | |||
"axios": "^0.18.0", | |||
"clipboard": "^2.0.0", | |||
"codemirror": "^5.38.0", | |||
"countup": "^1.8.2", | |||
"cropperjs": "^1.2.2", | |||
"dayjs": "^1.7.7", | |||
"echarts": "^4.0.4", | |||
"html2canvas": "^1.0.0-alpha.12", | |||
"iview": "^3.1.3", | |||
"iview-area": "^1.5.17", | |||
"js-cookie": "^2.2.0", | |||
"simplemde": "^1.11.2", | |||
"sortablejs": "^1.7.0", | |||
"tree-table-vue": "^1.1.0", | |||
"vue": "^2.5.10", | |||
"vue-i18n": "^7.8.0", | |||
"vue-router": "^3.0.1", | |||
"vuedraggable": "^2.16.0", | |||
"vuex": "^3.0.1", | |||
"wangeditor": "^3.1.1", | |||
"xlsx": "^0.13.3" | |||
}, | |||
"devDependencies": { | |||
"@vue/cli-plugin-babel": "^3.0.1", | |||
"@vue/cli-plugin-eslint": "^3.0.1", | |||
"@vue/cli-plugin-unit-mocha": "^3.0.1", | |||
"@vue/cli-service": "^3.0.1", | |||
"@vue/eslint-config-standard": "^3.0.0-beta.10", | |||
"@vue/test-utils": "^1.0.0-beta.10", | |||
"chai": "^4.1.2", | |||
"eslint-plugin-cypress": "^2.0.1", | |||
"less": "^2.7.3", | |||
"less-loader": "^4.0.5", | |||
"lint-staged": "^6.0.0", | |||
"mockjs": "^1.0.1-beta3", | |||
"vue-template-compiler": "^2.5.13" | |||
}, | |||
"browserslist": [ | |||
"> 1%", | |||
"last 2 versions", | |||
"not ie <= 8" | |||
], | |||
"gitHooks": { | |||
"pre-commit": "lint-staged" | |||
}, | |||
"lint-staged": { | |||
"*.js": [ | |||
"vue-cli-service lint", | |||
"git add" | |||
], | |||
"*.vue": [ | |||
"vue-cli-service lint", | |||
"git add" | |||
] | |||
} | |||
} |
@@ -1,92 +0,0 @@ | |||
<template> | |||
<div class="drag-list-wrapper"> | |||
<div class="drag-list-con con1"> | |||
<slot name="left-title"></slot> | |||
<draggable class="drop-box1" :class="dropConClass.left" :options="options" :value="list1" @input="handleListChange($event, 'left')" @end="handleEnd($event, 'left')"> | |||
<div class="drag-list-item" v-for="(itemLeft, index) in list1" :key="`drag_li1_${index}`"> | |||
<slot name="left" :itemLeft="itemLeft">{{ itemLeft }}</slot> | |||
</div> | |||
</draggable> | |||
</div> | |||
<div class="drag-list-con con2"> | |||
<slot name="right-title"></slot> | |||
<draggable class="drop-box2" :class="dropConClass.right" :options="options" :value="list2" @input="handleListChange($event, 'right')" @end="handleEnd($event, 'right')"> | |||
<div class="drag-list-item" v-for="(itemRight, index) in list2" :key="`drag_li2_${index}`"> | |||
<slot name="right" :itemRight="itemRight">{{ itemRight }}</slot> | |||
</div> | |||
</draggable> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import draggable from 'vuedraggable' | |||
export default { | |||
name: 'DragList', | |||
components: { | |||
draggable | |||
}, | |||
props: { | |||
list1: { | |||
type: Array, | |||
required: true | |||
}, | |||
list2: { | |||
type: Array, | |||
default: () => [] | |||
}, | |||
dropConClass: { | |||
type: Object, | |||
default: () => ({}) | |||
} | |||
}, | |||
data () { | |||
return { | |||
options: { group: 'drag_list' } | |||
} | |||
}, | |||
methods: { | |||
handleListChange (value, type) { | |||
if (type === 'left') this.$emit('update:list1', value) | |||
else this.$emit('update:list2', value) | |||
}, | |||
handleEnd (event, type) { | |||
const srcClassName = (event.srcElement || event.target).classList[0] | |||
const targetClassName = event.to.classList[0] | |||
let src = '' | |||
let target = '' | |||
if (srcClassName === targetClassName) { | |||
if (type === 'left') { | |||
src = 'left' | |||
target = 'left' | |||
} else { | |||
src = 'right' | |||
target = 'right' | |||
} | |||
} else { | |||
if (type === 'left') { | |||
src = 'left' | |||
target = 'right' | |||
} else { | |||
src = 'right' | |||
target = 'left' | |||
} | |||
} | |||
this.$emit('on-change', { | |||
src: src, | |||
target: target, | |||
oldIndex: event.oldIndex, | |||
newIndex: event.newIndex | |||
}) | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="less"> | |||
.drag-list-wrapper{ | |||
height: 100%; | |||
.drag-list-con{ | |||
width: 50%; | |||
float: left; | |||
} | |||
} | |||
</style> |
@@ -1,2 +0,0 @@ | |||
import DragList from './drag-list.vue' | |||
export default DragList |
@@ -1,75 +0,0 @@ | |||
<template> | |||
<div class="editor-wrapper"> | |||
<div :id="editorId"></div> | |||
</div> | |||
</template> | |||
<script> | |||
import Editor from 'wangeditor' | |||
import 'wangeditor/release/wangEditor.min.css' | |||
import { oneOf } from '@/libs/tools' | |||
export default { | |||
name: 'Editor', | |||
props: { | |||
value: { | |||
type: String, | |||
default: '' | |||
}, | |||
/** | |||
* 绑定的值的类型, enum: ['html', 'text'] | |||
*/ | |||
valueType: { | |||
type: String, | |||
default: 'html', | |||
validator: (val) => { | |||
return oneOf(val, ['html', 'text']) | |||
} | |||
}, | |||
/** | |||
* @description 设置change事件触发时间间隔 | |||
*/ | |||
changeInterval: { | |||
type: Number, | |||
default: 200 | |||
}, | |||
/** | |||
* @description 是否开启本地存储 | |||
*/ | |||
cache: { | |||
type: Boolean, | |||
default: true | |||
} | |||
}, | |||
computed: { | |||
editorId () { | |||
return `editor${this._uid}` | |||
} | |||
}, | |||
methods: { | |||
setHtml (val) { | |||
this.editor.txt.html(val) | |||
} | |||
}, | |||
mounted () { | |||
this.editor = new Editor(`#${this.editorId}`) | |||
this.editor.customConfig.onchange = (html) => { | |||
let text = this.editor.txt.text() | |||
if (this.cache) localStorage.editorCache = html | |||
this.$emit('input', this.valueType === 'html' ? html : text) | |||
this.$emit('on-change', html, text) | |||
} | |||
this.editor.customConfig.onchangeTimeout = this.changeInterval | |||
// create这个方法一定要在所有配置项之后调用 | |||
this.editor.create() | |||
// 如果本地有存储加载本地存储内容 | |||
let html = this.value || localStorage.editorCache | |||
if (html) this.editor.txt.html(html) | |||
} | |||
} | |||
</script> | |||
<style lang="less"> | |||
.editor-wrapper *{ | |||
z-index: 100 !important; | |||
} | |||
</style> |
@@ -1,2 +0,0 @@ | |||
import Editor from './editor.vue' | |||
export default Editor |
@@ -1,2 +0,0 @@ | |||
import MarkdownEditor from './markdown.vue' | |||
export default MarkdownEditor |
@@ -1,76 +0,0 @@ | |||
<template> | |||
<div class="markdown-wrapper"> | |||
<textarea ref="editor"></textarea> | |||
</div> | |||
</template> | |||
<script> | |||
import Simplemde from 'simplemde' | |||
import 'simplemde/dist/simplemde.min.css' | |||
export default { | |||
name: 'MarkdownEditor', | |||
props: { | |||
value: { | |||
type: String, | |||
default: '' | |||
}, | |||
options: { | |||
type: Object, | |||
default: () => { | |||
return {} | |||
} | |||
}, | |||
localCache: { | |||
type: Boolean, | |||
default: true | |||
} | |||
}, | |||
data () { | |||
return { | |||
editor: null | |||
} | |||
}, | |||
methods: { | |||
addEvents () { | |||
this.editor.codemirror.on('change', () => { | |||
let value = this.editor.value() | |||
if (this.localCache) localStorage.markdownContent = value | |||
this.$emit('input', value) | |||
this.$emit('on-change', value) | |||
}) | |||
this.editor.codemirror.on('focus', () => { | |||
this.$emit('on-focus', this.editor.value()) | |||
}) | |||
this.editor.codemirror.on('blur', () => { | |||
this.$emit('on-blur', this.editor.value()) | |||
}) | |||
} | |||
}, | |||
mounted () { | |||
this.editor = new Simplemde(Object.assign(this.options, { | |||
element: this.$refs.editor | |||
})) | |||
/** | |||
* 事件列表为Codemirror编辑器的事件,更多事件类型,请参考: | |||
* https://codemirror.net/doc/manual.html#events | |||
*/ | |||
this.addEvents() | |||
let content = localStorage.markdownContent | |||
if (content) this.editor.value(content) | |||
} | |||
} | |||
</script> | |||
<style lang="less"> | |||
.markdown-wrapper{ | |||
.editor-toolbar.fullscreen{ | |||
z-index: 9999; | |||
} | |||
.CodeMirror-fullscreen{ | |||
z-index: 9999; | |||
} | |||
.CodeMirror-fullscreen ~ .editor-preview-side{ | |||
z-index: 9999; | |||
} | |||
} | |||
</style> |
@@ -1,2 +0,0 @@ | |||
import PasteEditor from './paste-editor.vue' | |||
export default PasteEditor |
@@ -1,26 +0,0 @@ | |||
.paste-editor-wrapper{ | |||
width: 100%; | |||
height: 100%; | |||
border: 1px dashed gainsboro; | |||
textarea.textarea-el{ | |||
width: 100%; | |||
height: 100%; | |||
} | |||
.CodeMirror{ | |||
height: 100%; | |||
padding: 0; | |||
.CodeMirror-code div .CodeMirror-line > span > span.cm-tab{ | |||
&::after{ | |||
content: '→'; | |||
color: #BFBFBF; | |||
} | |||
} | |||
} | |||
.first-row{ | |||
font-weight: 700; | |||
font-size: 14px; | |||
} | |||
.incorrect-row{ | |||
background: #F5CBD1; | |||
} | |||
} |
@@ -1,115 +0,0 @@ | |||
<template> | |||
<div class="paste-editor-wrapper"> | |||
<textarea ref="codemirror" class="textarea-el"></textarea> | |||
</div> | |||
</template> | |||
<script> | |||
import CodeMirror from 'codemirror' | |||
import 'codemirror/lib/codemirror.css' | |||
import { forEach } from '@/libs/tools' | |||
import createPlaceholder from './plugins/placeholder' | |||
export default { | |||
name: 'PasteEditor', | |||
props: { | |||
value: Array, | |||
pasteData: { | |||
type: String, | |||
default: '' | |||
}, | |||
placeholder: { | |||
type: String, | |||
default: '从网页或其他应用软件复制表格数据,粘贴到这里 。默认第一行是表头,使用回车键添加新行,使用Tab键区分列。' | |||
} | |||
}, | |||
data () { | |||
return { | |||
pasteDataArr: [], | |||
rowArrLength: 0, | |||
editor: null | |||
} | |||
}, | |||
watch: { | |||
pasteData (val) { | |||
if (val === '') { | |||
this.editor.setValue('') | |||
} | |||
} | |||
}, | |||
computed: { | |||
rowNum () { | |||
return this.pasteDataArr.length | |||
}, | |||
colNum () { | |||
return this.pasteDataArr[0] ? this.pasteDataArr[0].length : 0 | |||
} | |||
}, | |||
methods: { | |||
handleKeyup (e) { | |||
this.handleAreaData() | |||
}, | |||
/** | |||
* @description 处理粘贴操作 | |||
*/ | |||
handleContentChanged (content) { | |||
let pasteData = content.trim() | |||
this.$emit('on-content-change', pasteData) | |||
let rows = pasteData.split((/[\n\u0085\u2028\u2029]|\r\n?/g)).map(row => { | |||
return row.split('\t') | |||
}) | |||
if (content === '') rows = [] | |||
this.pasteDataArr = rows | |||
this.clearLineClass() | |||
this.checkColNumInEveryRow() | |||
this.$emit('input', this.pasteDataArr) | |||
}, | |||
/** | |||
* @description 检查除第一行的每一行列数是否与第一行相同 | |||
*/ | |||
checkColNumInEveryRow () { | |||
let i = 0 | |||
const len = this.rowNum | |||
if (len === 0) return | |||
while (++i < len) { | |||
let item = this.pasteDataArr[i] | |||
if (item.length !== this.colNum && (!(i === len - 1 && item.length === 1 && item[0] === '') || i !== len - 1)) { | |||
this.markIncorrectRow(i) | |||
this.$emit('on-error', i) | |||
return false | |||
} | |||
} | |||
this.$emit('on-success', this.pasteDataArr) | |||
return true | |||
}, | |||
/** | |||
* @description 标记不符合格式的一行 | |||
*/ | |||
markIncorrectRow (index) { | |||
this.editor.addLineClass(index, 'text', 'incorrect-row') | |||
}, | |||
/** | |||
* @description 标记不符合格式的一行 | |||
*/ | |||
clearLineClass () { | |||
forEach(this.pasteDataArr, (item, index) => { | |||
this.editor.removeLineClass(index, 'text', 'incorrect-row') | |||
}) | |||
} | |||
}, | |||
mounted () { | |||
createPlaceholder(CodeMirror) | |||
this.editor = CodeMirror.fromTextArea(this.$refs.codemirror, { | |||
lineNumbers: true, | |||
tabSize: 1, | |||
lineWrapping: true, | |||
placeholder: this.placeholder | |||
}) | |||
this.editor.on('change', (editor) => { | |||
this.handleContentChanged(editor.getValue()) | |||
}) | |||
this.editor.addLineClass(0, 'text', 'first-row') | |||
} | |||
} | |||
</script> | |||
<style lang="less"> | |||
@import './paste-editor.less'; | |||
</style> |
@@ -1,58 +0,0 @@ | |||
export default (codemirror) => { | |||
(function (mod) { | |||
mod(codemirror) | |||
})(function (CodeMirror) { | |||
CodeMirror.defineOption('placeholder', '', function (cm, val, old) { | |||
var prev = old && old !== CodeMirror.Init | |||
if (val && !prev) { | |||
cm.on('blur', onBlur) | |||
cm.on('change', onChange) | |||
cm.on('swapDoc', onChange) | |||
onChange(cm) | |||
} else if (!val && prev) { | |||
cm.off('blur', onBlur) | |||
cm.off('change', onChange) | |||
cm.off('swapDoc', onChange) | |||
clearPlaceholder(cm) | |||
var wrapper = cm.getWrapperElement() | |||
wrapper.className = wrapper.className.replace(' CodeMirror-empty', '') | |||
} | |||
if (val && !cm.hasFocus()) onBlur(cm) | |||
}) | |||
function clearPlaceholder (cm) { | |||
if (cm.state.placeholder) { | |||
cm.state.placeholder.parentNode.removeChild(cm.state.placeholder) | |||
cm.state.placeholder = null | |||
} | |||
} | |||
function setPlaceholder (cm) { | |||
clearPlaceholder(cm) | |||
var elt = cm.state.placeholder = document.createElement('pre') | |||
elt.style.cssText = 'height: 0; overflow: visible; color: #80848f;' | |||
elt.style.direction = cm.getOption('direction') | |||
elt.className = 'CodeMirror-placeholder' | |||
var placeHolder = cm.getOption('placeholder') | |||
if (typeof placeHolder === 'string') placeHolder = document.createTextNode(placeHolder) | |||
elt.appendChild(placeHolder) | |||
cm.display.lineSpace.insertBefore(elt, cm.display.lineSpace.firstChild) | |||
} | |||
function onBlur (cm) { | |||
if (isEmpty(cm)) setPlaceholder(cm) | |||
} | |||
function onChange (cm) { | |||
let wrapper = cm.getWrapperElement() | |||
let empty = isEmpty(cm) | |||
wrapper.className = wrapper.className.replace(' CodeMirror-empty', '') + (empty ? ' CodeMirror-empty' : '') | |||
if (empty) setPlaceholder(cm) | |||
else clearPlaceholder(cm) | |||
} | |||
function isEmpty (cm) { | |||
return (cm.lineCount() === 1) && (cm.getLine(0) === '') | |||
} | |||
}) | |||
} |
@@ -1,92 +0,0 @@ | |||
<template> | |||
<div class="drag-list-wrapper"> | |||
<div class="drag-list-con con1"> | |||
<slot name="left-title"></slot> | |||
<draggable class="drop-box1" :class="dropConClass.left" :options="options" :value="list1" @input="handleListChange($event, 'left')" @end="handleEnd($event, 'left')"> | |||
<div class="drag-list-item" v-for="(itemLeft, index) in list1" :key="`drag_li1_${index}`"> | |||
<slot name="left" :itemLeft="itemLeft">{{ itemLeft }}</slot> | |||
</div> | |||
</draggable> | |||
</div> | |||
<div class="drag-list-con con2"> | |||
<slot name="right-title"></slot> | |||
<draggable class="drop-box2" :class="dropConClass.right" :options="options" :value="list2" @input="handleListChange($event, 'right')" @end="handleEnd($event, 'right')"> | |||
<div class="drag-list-item" v-for="(itemRight, index) in list2" :key="`drag_li2_${index}`"> | |||
<slot name="right" :itemRight="itemRight">{{ itemRight }}</slot> | |||
</div> | |||
</draggable> | |||
</div> | |||
</div> | |||
</template> | |||
<script> | |||
import draggable from 'vuedraggable' | |||
export default { | |||
name: 'DragList', | |||
components: { | |||
draggable | |||
}, | |||
props: { | |||
list1: { | |||
type: Array, | |||
required: true | |||
}, | |||
list2: { | |||
type: Array, | |||
default: () => [] | |||
}, | |||
dropConClass: { | |||
type: Object, | |||
default: () => ({}) | |||
} | |||
}, | |||
data () { | |||
return { | |||
options: { group: 'drag_list' } | |||
} | |||
}, | |||
methods: { | |||
handleListChange (value, type) { | |||
if (type === 'left') this.$emit('update:list1', value) | |||
else this.$emit('update:list2', value) | |||
}, | |||
handleEnd (event, type) { | |||
const srcClassName = (event.srcElement || event.target).classList[0] | |||
const targetClassName = event.to.classList[0] | |||
let src = '' | |||
let target = '' | |||
if (srcClassName === targetClassName) { | |||
if (type === 'left') { | |||
src = 'left' | |||
target = 'left' | |||
} else { | |||
src = 'right' | |||
target = 'right' | |||
} | |||
} else { | |||
if (type === 'left') { | |||
src = 'left' | |||
target = 'right' | |||
} else { | |||
src = 'right' | |||
target = 'left' | |||
} | |||
} | |||
this.$emit('on-change', { | |||
src: src, | |||
target: target, | |||
oldIndex: event.oldIndex, | |||
newIndex: event.newIndex | |||
}) | |||
} | |||
} | |||
} | |||
</script> | |||
<style lang="less"> | |||
.drag-list-wrapper{ | |||
height: 100%; | |||
.drag-list-con{ | |||
width: 50%; | |||
float: left; | |||
} | |||
} | |||
</style> |
@@ -1,2 +0,0 @@ | |||
import DragList from './drag-list.vue' | |||
export default DragList |
@@ -1,75 +0,0 @@ | |||
<template> | |||
<div class="editor-wrapper"> | |||
<div :id="editorId"></div> | |||
</div> | |||
</template> | |||
<script> | |||
import Editor from 'wangeditor' | |||
import 'wangeditor/release/wangEditor.min.css' | |||
import { oneOf } from '@/libs/tools' | |||
export default { | |||
name: 'Editor', | |||
props: { | |||
value: { | |||
type: String, | |||
default: '' | |||
}, | |||
/** | |||
* 绑定的值的类型, enum: ['html', 'text'] | |||
*/ | |||
valueType: { | |||
type: String, | |||
default: 'html', | |||
validator: (val) => { | |||
return oneOf(val, ['html', 'text']) | |||
} | |||
}, | |||
/** | |||
* @description 设置change事件触发时间间隔 | |||
*/ | |||
changeInterval: { | |||
type: Number, | |||
default: 200 | |||
}, | |||
/** | |||
* @description 是否开启本地存储 | |||
*/ | |||
cache: { | |||
type: Boolean, | |||
default: true | |||
} | |||
}, | |||
computed: { | |||
editorId () { | |||
return `editor${this._uid}` | |||
} | |||
}, | |||
methods: { | |||
setHtml (val) { | |||
this.editor.txt.html(val) | |||
} | |||
}, | |||
mounted () { | |||
this.editor = new Editor(`#${this.editorId}`) | |||
this.editor.customConfig.onchange = (html) => { | |||
let text = this.editor.txt.text() | |||
if (this.cache) localStorage.editorCache = html | |||
this.$emit('input', this.valueType === 'html' ? html : text) | |||
this.$emit('on-change', html, text) | |||
} | |||
this.editor.customConfig.onchangeTimeout = this.changeInterval | |||
// create这个方法一定要在所有配置项之后调用 | |||
this.editor.create() | |||
// 如果本地有存储加载本地存储内容 | |||
let html = this.value || localStorage.editorCache | |||
if (html) this.editor.txt.html(html) | |||
} | |||
} | |||
</script> | |||
<style lang="less"> | |||
.editor-wrapper *{ | |||
z-index: 100 !important; | |||
} | |||
</style> |
@@ -1,2 +0,0 @@ | |||
import Editor from './editor.vue' | |||
export default Editor |
@@ -1,2 +0,0 @@ | |||
import MarkdownEditor from './markdown.vue' | |||
export default MarkdownEditor |
@@ -1,76 +0,0 @@ | |||
<template> | |||
<div class="markdown-wrapper"> | |||
<textarea ref="editor"></textarea> | |||
</div> | |||
</template> | |||
<script> | |||
import Simplemde from 'simplemde' | |||
import 'simplemde/dist/simplemde.min.css' | |||
export default { | |||
name: 'MarkdownEditor', | |||
props: { | |||
value: { | |||
type: String, | |||
default: '' | |||
}, | |||
options: { | |||
type: Object, | |||
default: () => { | |||
return {} | |||
} | |||
}, | |||
localCache: { | |||
type: Boolean, | |||
default: true | |||
} | |||
}, | |||
data () { | |||
return { | |||
editor: null | |||
} | |||
}, | |||
methods: { | |||
addEvents () { | |||
this.editor.codemirror.on('change', () => { | |||
let value = this.editor.value() | |||
if (this.localCache) localStorage.markdownContent = value | |||
this.$emit('input', value) | |||
this.$emit('on-change', value) | |||
}) | |||
this.editor.codemirror.on('focus', () => { | |||
this.$emit('on-focus', this.editor.value()) | |||
}) | |||
this.editor.codemirror.on('blur', () => { | |||
this.$emit('on-blur', this.editor.value()) | |||
}) | |||
} | |||
}, | |||
mounted () { | |||
this.editor = new Simplemde(Object.assign(this.options, { | |||
element: this.$refs.editor | |||
})) | |||
/** | |||
* 事件列表为Codemirror编辑器的事件,更多事件类型,请参考: | |||
* https://codemirror.net/doc/manual.html#events | |||
*/ | |||
this.addEvents() | |||
let content = localStorage.markdownContent | |||
if (content) this.editor.value(content) | |||
} | |||
} | |||
</script> | |||
<style lang="less"> | |||
.markdown-wrapper{ | |||
.editor-toolbar.fullscreen{ | |||
z-index: 9999; | |||
} | |||
.CodeMirror-fullscreen{ | |||
z-index: 9999; | |||
} | |||
.CodeMirror-fullscreen ~ .editor-preview-side{ | |||
z-index: 9999; | |||
} | |||
} | |||
</style> |
@@ -1,2 +0,0 @@ | |||
import PasteEditor from './paste-editor.vue' | |||
export default PasteEditor |
@@ -1,26 +0,0 @@ | |||
.paste-editor-wrapper{ | |||
width: 100%; | |||
height: 100%; | |||
border: 1px dashed gainsboro; | |||
textarea.textarea-el{ | |||
width: 100%; | |||
height: 100%; | |||
} | |||
.CodeMirror{ | |||
height: 100%; | |||
padding: 0; | |||
.CodeMirror-code div .CodeMirror-line > span > span.cm-tab{ | |||
&::after{ | |||
content: '→'; | |||
color: #BFBFBF; | |||
} | |||
} | |||
} | |||
.first-row{ | |||
font-weight: 700; | |||
font-size: 14px; | |||
} | |||
.incorrect-row{ | |||
background: #F5CBD1; | |||
} | |||
} |
@@ -1,115 +0,0 @@ | |||
<template> | |||
<div class="paste-editor-wrapper"> | |||
<textarea ref="codemirror" class="textarea-el"></textarea> | |||
</div> | |||
</template> | |||
<script> | |||
import CodeMirror from 'codemirror' | |||
import 'codemirror/lib/codemirror.css' | |||
import { forEach } from '@/libs/tools' | |||
import createPlaceholder from './plugins/placeholder' | |||
export default { | |||
name: 'PasteEditor', | |||
props: { | |||
value: Array, | |||
pasteData: { | |||
type: String, | |||
default: '' | |||
}, | |||
placeholder: { | |||
type: String, | |||
default: '从网页或其他应用软件复制表格数据,粘贴到这里 。默认第一行是表头,使用回车键添加新行,使用Tab键区分列。' | |||
} | |||
}, | |||
data () { | |||
return { | |||
pasteDataArr: [], | |||
rowArrLength: 0, | |||
editor: null | |||
} | |||
}, | |||
watch: { | |||
pasteData (val) { | |||
if (val === '') { | |||
this.editor.setValue('') | |||
} | |||
} | |||
}, | |||
computed: { | |||
rowNum () { | |||
return this.pasteDataArr.length | |||
}, | |||
colNum () { | |||
return this.pasteDataArr[0] ? this.pasteDataArr[0].length : 0 | |||
} | |||
}, | |||
methods: { | |||
handleKeyup (e) { | |||
this.handleAreaData() | |||
}, | |||
/** | |||
* @description 处理粘贴操作 | |||
*/ | |||
handleContentChanged (content) { | |||
let pasteData = content.trim() | |||
this.$emit('on-content-change', pasteData) | |||
let rows = pasteData.split((/[\n\u0085\u2028\u2029]|\r\n?/g)).map(row => { | |||
return row.split('\t') | |||
}) | |||
if (content === '') rows = [] | |||
this.pasteDataArr = rows | |||
this.clearLineClass() | |||
this.checkColNumInEveryRow() | |||
this.$emit('input', this.pasteDataArr) | |||
}, | |||
/** | |||
* @description 检查除第一行的每一行列数是否与第一行相同 | |||
*/ | |||
checkColNumInEveryRow () { | |||
let i = 0 | |||
const len = this.rowNum | |||
if (len === 0) return | |||
while (++i < len) { | |||
let item = this.pasteDataArr[i] | |||
if (item.length !== this.colNum && (!(i === len - 1 && item.length === 1 && item[0] === '') || i !== len - 1)) { | |||
this.markIncorrectRow(i) | |||
this.$emit('on-error', i) | |||
return false | |||
} | |||
} | |||
this.$emit('on-success', this.pasteDataArr) | |||
return true | |||
}, | |||
/** | |||
* @description 标记不符合格式的一行 | |||
*/ | |||
markIncorrectRow (index) { | |||
this.editor.addLineClass(index, 'text', 'incorrect-row') | |||
}, | |||
/** | |||
* @description 标记不符合格式的一行 | |||
*/ | |||
clearLineClass () { | |||
forEach(this.pasteDataArr, (item, index) => { | |||
this.editor.removeLineClass(index, 'text', 'incorrect-row') | |||
}) | |||
} | |||
}, | |||
mounted () { | |||
createPlaceholder(CodeMirror) | |||
this.editor = CodeMirror.fromTextArea(this.$refs.codemirror, { | |||
lineNumbers: true, | |||
tabSize: 1, | |||
lineWrapping: true, | |||
placeholder: this.placeholder | |||
}) | |||
this.editor.on('change', (editor) => { | |||
this.handleContentChanged(editor.getValue()) | |||
}) | |||
this.editor.addLineClass(0, 'text', 'first-row') | |||
} | |||
} | |||
</script> | |||
<style lang="less"> | |||
@import './paste-editor.less'; | |||
</style> |
@@ -1,58 +0,0 @@ | |||
export default (codemirror) => { | |||
(function (mod) { | |||
mod(codemirror) | |||
})(function (CodeMirror) { | |||
CodeMirror.defineOption('placeholder', '', function (cm, val, old) { | |||
var prev = old && old !== CodeMirror.Init | |||
if (val && !prev) { | |||
cm.on('blur', onBlur) | |||
cm.on('change', onChange) | |||
cm.on('swapDoc', onChange) | |||
onChange(cm) | |||
} else if (!val && prev) { | |||
cm.off('blur', onBlur) | |||
cm.off('change', onChange) | |||
cm.off('swapDoc', onChange) | |||
clearPlaceholder(cm) | |||
var wrapper = cm.getWrapperElement() | |||
wrapper.className = wrapper.className.replace(' CodeMirror-empty', '') | |||
} | |||
if (val && !cm.hasFocus()) onBlur(cm) | |||
}) | |||
function clearPlaceholder (cm) { | |||
if (cm.state.placeholder) { | |||
cm.state.placeholder.parentNode.removeChild(cm.state.placeholder) | |||
cm.state.placeholder = null | |||
} | |||
} | |||
function setPlaceholder (cm) { | |||
clearPlaceholder(cm) | |||
var elt = cm.state.placeholder = document.createElement('pre') | |||
elt.style.cssText = 'height: 0; overflow: visible; color: #80848f;' | |||
elt.style.direction = cm.getOption('direction') | |||
elt.className = 'CodeMirror-placeholder' | |||
var placeHolder = cm.getOption('placeholder') | |||
if (typeof placeHolder === 'string') placeHolder = document.createTextNode(placeHolder) | |||
elt.appendChild(placeHolder) | |||
cm.display.lineSpace.insertBefore(elt, cm.display.lineSpace.firstChild) | |||
} | |||
function onBlur (cm) { | |||
if (isEmpty(cm)) setPlaceholder(cm) | |||
} | |||
function onChange (cm) { | |||
let wrapper = cm.getWrapperElement() | |||
let empty = isEmpty(cm) | |||
wrapper.className = wrapper.className.replace(' CodeMirror-empty', '') + (empty ? ' CodeMirror-empty' : '') | |||
if (empty) setPlaceholder(cm) | |||
else clearPlaceholder(cm) | |||
} | |||
function isEmpty (cm) { | |||
return (cm.lineCount() === 1) && (cm.getLine(0) === '') | |||
} | |||
}) | |||
} |
@@ -1,12 +0,0 @@ | |||
{ | |||
"plugins": [ | |||
"cypress" | |||
], | |||
"env": { | |||
"mocha": true, | |||
"cypress/globals": true | |||
}, | |||
"rules": { | |||
"strict": "off" | |||
} | |||
} |
@@ -1,9 +0,0 @@ | |||
// https://docs.cypress.io/guides/guides/plugins-guide.html | |||
module.exports = (on, config) => Object.assign({}, config, { | |||
fixturesFolder: 'tests/e2e/fixtures', | |||
integrationFolder: 'tests/e2e/specs', | |||
screenshotsFolder: 'tests/e2e/screenshots', | |||
videosFolder: 'tests/e2e/videos', | |||
supportFile: 'tests/e2e/support/index.js' | |||
}) |
@@ -1,8 +0,0 @@ | |||
// https://docs.cypress.io/api/introduction/api.html | |||
describe('My First Test', () => { | |||
it('Visits the app root url', () => { | |||
cy.visit('/') | |||
cy.contains('h1', 'Welcome to Your Vue.js App') | |||
}) | |||
}) |
@@ -1,25 +0,0 @@ | |||
// *********************************************** | |||
// This example commands.js shows you how to | |||
// create various custom commands and overwrite | |||
// existing commands. | |||
// | |||
// For more comprehensive examples of custom | |||
// commands please read more here: | |||
// https://on.cypress.io/custom-commands | |||
// *********************************************** | |||
// | |||
// | |||
// -- This is a parent command -- | |||
// Cypress.Commands.add("login", (email, password) => { ... }) | |||
// | |||
// | |||
// -- This is a child command -- | |||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) | |||
// | |||
// | |||
// -- This is a dual command -- | |||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) | |||
// | |||
// | |||
// -- This is will overwrite an existing command -- | |||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) |
@@ -1,20 +0,0 @@ | |||
// *********************************************************** | |||
// This example support/index.js is processed and | |||
// loaded automatically before your test files. | |||
// | |||
// This is a great place to put global configuration and | |||
// behavior that modifies Cypress. | |||
// | |||
// You can change the location of this file or turn off | |||
// automatically serving support files with the | |||
// 'supportFile' configuration option. | |||
// | |||
// You can read more here: | |||
// https://on.cypress.io/configuration | |||
// *********************************************************** | |||
// Import commands.js using ES2015 syntax: | |||
import './commands' | |||
// Alternatively you can use CommonJS syntax: | |||
// require('./commands') |
@@ -1,8 +0,0 @@ | |||
module.exports = { | |||
env: { | |||
mocha: true | |||
}, | |||
rules: { | |||
'import/no-extraneous-dependencies': 'off' | |||
} | |||
} |
@@ -1,13 +0,0 @@ | |||
import { expect } from 'chai' | |||
import { shallow } from '@vue/test-utils' | |||
import HelloWorld from '@/components/HelloWorld.vue' | |||
describe('HelloWorld.vue', () => { | |||
it('renders props.msg when passed', () => { | |||
const msg = 'new message' | |||
const wrapper = shallow(HelloWorld, { | |||
propsData: { msg } | |||
}) | |||
expect(wrapper.text()).to.include(msg) | |||
}) | |||
}) |