mirror of
https://gitee.com/orangeform/orange-admin.git
synced 2026-01-18 02:56:30 +08:00
commit:同步2.3版本
This commit is contained in:
@@ -0,0 +1,305 @@
|
||||
<template>
|
||||
<div class="date-range">
|
||||
<!--<year-range-panel />-->
|
||||
<el-select v-model="dateType" :size="size"
|
||||
style="max-width: 100px; min-width: 100px; margin-right: 10px;"
|
||||
v-if="!hideTypeOnlyOne || validTypeList.length > 1">
|
||||
<el-option v-for="type in validTypeList" :key="type.value" :value="type.value" :label="type.label" />
|
||||
</el-select>
|
||||
<el-date-picker style="flex-grow: 1;" v-model="currentDates"
|
||||
:size="size" :placeholder="placeholder" :type="innerDateType"
|
||||
:disabled="disabled" :format="innerDateFormat" :readonly="readonly" :editable="editable"
|
||||
:clearable="clearable" :start-placeholder="startPlaceholder" :end-placeholder="endPlaceholder"
|
||||
:align="align" :range-separator="rangeSeparator" :value-format="valueFormat"
|
||||
:prefix-icon="prefixIcon" :clear-icon="clearIcon" @change="onValueChange" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { formatDate, parseDate } from 'element-ui/src/utils/date-util';
|
||||
|
||||
const allTypeList = [{
|
||||
value: 'day',
|
||||
label: '自然日'
|
||||
}, {
|
||||
value: 'month',
|
||||
label: '自然月'
|
||||
}, {
|
||||
value: 'year',
|
||||
label: '自然年'
|
||||
}];
|
||||
|
||||
export default {
|
||||
name: 'DateRange',
|
||||
props: {
|
||||
/**
|
||||
* 绑定的数据
|
||||
*/
|
||||
value: {
|
||||
type: [Array],
|
||||
default: () => {
|
||||
return []
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 默认显示的数据选择方式,如果不存在与allowTypes中则显示allowTypes中的第一个
|
||||
*/
|
||||
defaultDateType: {
|
||||
type: String,
|
||||
default: 'day'
|
||||
},
|
||||
/**
|
||||
* 组件大小(medium / small / mini)
|
||||
*/
|
||||
size: {
|
||||
type: String
|
||||
},
|
||||
/**
|
||||
* 数据选择方式只有一个的时候是否隐藏数据选择方式下拉
|
||||
*/
|
||||
hideTypeOnlyOne: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/**
|
||||
* 允许的数据选择方式(day, month, year)
|
||||
* 默认值['day', 'month', 'year']
|
||||
*/
|
||||
allowTypes: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return allTypeList.map((item) => {
|
||||
return item.value;
|
||||
});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 是否范围选择
|
||||
*/
|
||||
isRange: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/**
|
||||
* 输出字符串的format
|
||||
*/
|
||||
outputFormat: {
|
||||
type: String,
|
||||
default: 'yyyy-MM-dd HH:mm:ss'
|
||||
},
|
||||
/**
|
||||
* 非范围选择时的占位内容
|
||||
*/
|
||||
placeholder: String,
|
||||
/**
|
||||
* 范围选择时开始日期的占位内容
|
||||
*/
|
||||
startPlaceholder: String,
|
||||
/**
|
||||
* 范围选择时结束日期的占位内容
|
||||
*/
|
||||
endPlaceholder: String,
|
||||
/**
|
||||
* 完全只读
|
||||
*/
|
||||
readonly: Boolean,
|
||||
/**
|
||||
* 禁用
|
||||
*/
|
||||
disabled: Boolean,
|
||||
/**
|
||||
* 文本框可输入
|
||||
*/
|
||||
editable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/**
|
||||
* 是否显示清除按钮
|
||||
*/
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/**
|
||||
* 对齐方式(left, center, right)
|
||||
*/
|
||||
align: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
/**
|
||||
* 选择范围时的分隔符
|
||||
*/
|
||||
rangeSeparator: {
|
||||
type: String,
|
||||
default: '-'
|
||||
},
|
||||
/**
|
||||
* 可选,绑定值的格式。不指定则绑定值为 Date 对象
|
||||
*/
|
||||
valueFormat: {
|
||||
type: String,
|
||||
default: 'yyyy-MM-dd'
|
||||
},
|
||||
/**
|
||||
* 自定义头部图标的类名
|
||||
*/
|
||||
prefixIcon: {
|
||||
type: String,
|
||||
default: 'el-icon-date'
|
||||
},
|
||||
/**
|
||||
* 自定义清空图标的类名
|
||||
*/
|
||||
clearIcon: {
|
||||
type: String,
|
||||
default: 'el-icon-circle-close'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
dateType: this.defaultDateType,
|
||||
currentDates: undefined
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onValueChange (values) {
|
||||
this.$nextTick(() => {
|
||||
this.emitChange();
|
||||
});
|
||||
},
|
||||
emitChange () {
|
||||
let outputDate = [];
|
||||
if (this.currentDates != null) {
|
||||
if (!this.isRange) {
|
||||
outputDate[0] = new Date(this.currentDates);
|
||||
outputDate[1] = new Date(this.currentDates);
|
||||
} else {
|
||||
if (Array.isArray(this.currentDates) && this.currentDates.length === 2) {
|
||||
outputDate[0] = new Date(this.currentDates[0]);
|
||||
outputDate[1] = new Date(this.currentDates[1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (outputDate[0] != null && outputDate[1] != null) {
|
||||
outputDate[0].setHours(0, 0, 0, 0);
|
||||
outputDate[1].setHours(0, 0, 0, 0);
|
||||
switch (this.dateType) {
|
||||
case 'day':
|
||||
outputDate[1].setDate(outputDate[1].getDate() + 1);
|
||||
break;
|
||||
case 'month':
|
||||
outputDate[1].setDate(1);
|
||||
outputDate[0].setDate(1);
|
||||
outputDate[1].setMonth(outputDate[1].getMonth() + 1);
|
||||
break;
|
||||
case 'year':
|
||||
outputDate[1].setMonth(0);
|
||||
outputDate[1].setDate(1);
|
||||
outputDate[0].setMonth(0);
|
||||
outputDate[0].setDate(1);
|
||||
outputDate[1].setFullYear(outputDate[1].getFullYear() + 1);
|
||||
break;
|
||||
}
|
||||
outputDate[1] = new Date(outputDate[1].getTime() - 1);
|
||||
|
||||
outputDate[0] = formatDate(outputDate[0], this.outputFormat);
|
||||
outputDate[1] = formatDate(outputDate[1], this.outputFormat);
|
||||
}
|
||||
}
|
||||
this.$emit('input', outputDate);
|
||||
this.$emit('change', outputDate);
|
||||
},
|
||||
getCurrentStatsDateType () {
|
||||
return this.dateType;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
validTypeList () {
|
||||
return allTypeList.filter((item) => {
|
||||
return this.allowTypes.indexOf(item.value) !== -1;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* el-date-picker使用的type
|
||||
*/
|
||||
innerDateType () {
|
||||
switch (this.dateType) {
|
||||
case 'day': return this.isRange ? 'daterange' : 'date';
|
||||
case 'month': return this.isRange ? 'monthrange' : 'month';
|
||||
case 'year': return this.isRange ? 'monthrange' : 'year';
|
||||
default: return this.isRange ? 'daterange' : 'date';
|
||||
}
|
||||
},
|
||||
/**
|
||||
* el-date-picker使用的format
|
||||
*/
|
||||
innerDateFormat () {
|
||||
switch (this.dateType) {
|
||||
case 'day': return 'yyyy-MM-dd';
|
||||
case 'month': return 'yyyy-MM';
|
||||
case 'year': return 'yyyy';
|
||||
default: return 'yyyy-MM-dd';
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler: function (newValue, oldValue) {
|
||||
if (newValue == null || newValue.length < 2) {
|
||||
this.currentDates = this.isRange ? [] : undefined;
|
||||
} else {
|
||||
if (this.currentDates == null) this.currentDates = [];
|
||||
if (this.isRange) {
|
||||
this.currentDates = [
|
||||
parseDate(newValue[0], this.valueFormat),
|
||||
parseDate(newValue[1], this.valueFormat)
|
||||
];
|
||||
} else {
|
||||
this.currentDates = parseDate(newValue[0], this.valueFormat);
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
},
|
||||
dateType: {
|
||||
handler: function (newValue, oldValue) {
|
||||
if (this.allowTypes.indexOf(this.dateType) === -1) {
|
||||
this.dateType = this.allowTypes[0] || 'day';
|
||||
}
|
||||
this.emitChange();
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
defaultDateType: {
|
||||
handler: function (newValue, oldValue) {
|
||||
if (this.allowTypes.indexOf(newValue) !== -1) {
|
||||
this.dateType = newValue;
|
||||
} else {
|
||||
this.dateType = this.allowTypes[0];
|
||||
}
|
||||
}
|
||||
},
|
||||
isRange: {
|
||||
handler: function (newValue, oldValue) {
|
||||
let temp;
|
||||
if (newValue) {
|
||||
temp = [this.currentDates, this.currentDates];
|
||||
} else {
|
||||
temp = this.currentDates[0];
|
||||
}
|
||||
|
||||
this.currentDates = temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.date-range {
|
||||
display: flex;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,77 @@
|
||||
import $ from 'jquery';
|
||||
import Vue from 'vue';
|
||||
import router from '@/router';
|
||||
import store from '@/store';
|
||||
|
||||
window.jQuery = $;
|
||||
const layer = require('layui-layer');
|
||||
|
||||
class Dialog {
|
||||
/**
|
||||
* 关闭弹窗
|
||||
* @param {*} index 要关闭的弹窗的index
|
||||
*/
|
||||
static close (index) {
|
||||
layer.close(index);
|
||||
}
|
||||
/**
|
||||
* 关闭所有弹窗
|
||||
*/
|
||||
static closeAll () {
|
||||
layer.closeAll();
|
||||
}
|
||||
/**
|
||||
* 打开弹窗
|
||||
* @param {*} title 弹窗标题
|
||||
* @param {*} component 弹窗内容的组件
|
||||
* @param {*} options 弹窗设置(详情请见layui官网)
|
||||
* @param {*} params 弹窗组件参数
|
||||
*/
|
||||
static show (title, component, options, params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let layerOptions = {
|
||||
title: title,
|
||||
type: 1,
|
||||
skin: 'layer-dialog',
|
||||
resize: false,
|
||||
offset: 'auto',
|
||||
zIndex: 1000,
|
||||
index: 0,
|
||||
contentDom: null
|
||||
};
|
||||
|
||||
layerOptions = {...layerOptions, ...options};
|
||||
layerOptions.end = () => {
|
||||
if (layerOptions.contentDom) document.body.removeChild(layerOptions.contentDom);
|
||||
}
|
||||
|
||||
let observer = {
|
||||
cancel: function (isSuccess = false, data = undefined) {
|
||||
layer.close(this.index);
|
||||
if (isSuccess) {
|
||||
resolve(data);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
},
|
||||
index: -1
|
||||
}
|
||||
layerOptions.cancel = () => {
|
||||
reject();
|
||||
}
|
||||
let dom = document.createElement('div');
|
||||
document.body.appendChild(dom);
|
||||
let Content = Vue.extend(component);
|
||||
let vueObj = new Content({router: router, store: store, propsData: params});
|
||||
vueObj.observer = observer;
|
||||
vueObj.$mount(dom);
|
||||
layerOptions.contentDom = vueObj.$el;
|
||||
layerOptions.content = $(layerOptions.contentDom);
|
||||
observer.index = layer.open(layerOptions);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Vue.prototype.$dialog = Dialog;
|
||||
|
||||
export default Dialog;
|
||||
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<el-row class="flex-box" type="flex" :justify="rowJustify">
|
||||
<slot />
|
||||
<div v-for="item in tempDomCount" :key="item" :style="{width: tempDomWidth}" />
|
||||
<el-row type="flex" :justify="operatorPosition" :style="getMenuBoxStyle">
|
||||
<slot name="operator" />
|
||||
</el-row>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import $ from 'jquery';
|
||||
|
||||
export default {
|
||||
name: 'FilterBox',
|
||||
props: {
|
||||
/**
|
||||
* 每一个过滤项宽度(包含标题和输入框宽度总和)
|
||||
*/
|
||||
itemWidth: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
/**
|
||||
* 每一项下间距
|
||||
*/
|
||||
marginBottom: {
|
||||
type: String,
|
||||
default: '18px'
|
||||
},
|
||||
/**
|
||||
* 按钮块最小宽度默认350,当每一行剩余空间大于此值,按钮块将不会折行
|
||||
*/
|
||||
minMenuWidth: {
|
||||
type: Number,
|
||||
default: 350
|
||||
},
|
||||
/**
|
||||
* 按钮位置,默认为end,可选值为start/end/center/space-around/space-between
|
||||
*/
|
||||
operatorPosition: {
|
||||
type: String,
|
||||
default: 'end'
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
tempDomCount: 0,
|
||||
tempDomWidth: undefined,
|
||||
operatorWidth: undefined,
|
||||
oldFilterItemCount: 0,
|
||||
oldHasOperator: false,
|
||||
oldWidth: 0,
|
||||
rowJustify: 'space-between'
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getMenuBoxStyle () {
|
||||
return {
|
||||
'width': this.operatorWidth,
|
||||
'margin-bottom': this.marginBottom,
|
||||
'flex-grow': this.operatorWidth ? undefined : '1'
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onUpdate () {
|
||||
setTimeout(() => {
|
||||
let filterItemCount = Array.isArray(this.$slots.default) ? this.$slots.default.filter(item => item.context).length : 0;
|
||||
let hasOperator = Array.isArray(this.$slots.operator) && this.$slots.operator.length > 0;
|
||||
let width = $(this.$el).width();
|
||||
if (filterItemCount === this.oldFilterItemCount && hasOperator === this.oldHasOperator && width === this.oldWidth) {
|
||||
return;
|
||||
}
|
||||
let lineCount = this.itemWidth > 0 ? parseInt(width / this.itemWidth) : 1;
|
||||
lineCount = Math.max(1, lineCount);
|
||||
let residueCount = filterItemCount % lineCount;
|
||||
|
||||
this.tempDomCount = 0;
|
||||
this.tempDomWidth = undefined;
|
||||
this.rowJustify = 'space-between';
|
||||
let tempCount = residueCount === 0 ? 0 : (lineCount - residueCount);
|
||||
if (hasOperator) {
|
||||
let residueWidth = width - ((Math.min(lineCount, filterItemCount) - residueCount) * this.itemWidth) - ((tempCount >= 1) ? 20 : 0);
|
||||
// 判断剩余的空间是否够放下操作按钮
|
||||
if (residueWidth >= this.minMenuWidth && residueCount === 0) {
|
||||
this.rowJustify = 'start';
|
||||
this.operatorWidth = undefined;
|
||||
} else {
|
||||
// 剩余空位数大于1,需要占位dom
|
||||
if (tempCount >= 1) {
|
||||
if (residueWidth >= this.minMenuWidth) {
|
||||
this.tempDomCount = tempCount - 1;
|
||||
this.tempDomWidth = this.tempDomCount > 0 ? (20 / this.tempDomCount) + 'px' : undefined;
|
||||
this.operatorWidth = this.tempDomCount > 0 ? (((tempCount * this.itemWidth) - 20) + 'px') : (this.itemWidth + 'px');
|
||||
} else {
|
||||
this.tempDomCount = tempCount;
|
||||
this.tempDomWidth = (residueWidth / this.tempDomCount) + 'px';
|
||||
this.operatorWidth = '100%';
|
||||
}
|
||||
} else {
|
||||
this.operatorWidth = '100%';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.tempDomCount = tempCount;
|
||||
this.tempDomWidth = this.itemWidth + 'px';
|
||||
}
|
||||
|
||||
this.oldFilterItemCount = filterItemCount;
|
||||
this.oldHasOperator = hasOperator;
|
||||
this.oldWidth = width;
|
||||
});
|
||||
}
|
||||
},
|
||||
beforeUpdate () {
|
||||
this.onUpdate();
|
||||
},
|
||||
mounted () {
|
||||
setTimeout(() => {
|
||||
this.onUpdate();
|
||||
});
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('resize', this.onUpdate);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('resize', this.onUpdate);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<div>
|
||||
<svg t="1492500959545" @click="toggleClick" class="hamburger" :class="{'is-active':isActive}" style="" viewBox="0 0 1024 1024"
|
||||
version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1691" xmlns:xlink="http://www.w3.org/1999/xlink" width="64" height="64">
|
||||
<path d="M966.8023 568.849776 57.196677 568.849776c-31.397081 0-56.850799-25.452695-56.850799-56.850799l0 0c0-31.397081 25.452695-56.849776 56.850799-56.849776l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.849776l0 0C1023.653099 543.397081 998.200404 568.849776 966.8023 568.849776z"
|
||||
p-id="1692"></path>
|
||||
<path d="M966.8023 881.527125 57.196677 881.527125c-31.397081 0-56.850799-25.452695-56.850799-56.849776l0 0c0-31.397081 25.452695-56.849776 56.850799-56.849776l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.849776l0 0C1023.653099 856.07443 998.200404 881.527125 966.8023 881.527125z"
|
||||
p-id="1693"></path>
|
||||
<path d="M966.8023 256.17345 57.196677 256.17345c-31.397081 0-56.850799-25.452695-56.850799-56.849776l0 0c0-31.397081 25.452695-56.850799 56.850799-56.850799l909.605623 0c31.397081 0 56.849776 25.452695 56.849776 56.850799l0 0C1023.653099 230.720755 998.200404 256.17345 966.8023 256.17345z"
|
||||
p-id="1694"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'hamburger',
|
||||
props: {
|
||||
isActive: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
toggleClick: {
|
||||
type: Function,
|
||||
default: null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.hamburger {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transform: rotate(90deg);
|
||||
transform-origin: 50% 50%;
|
||||
}
|
||||
|
||||
.hamburger.is-active {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,280 @@
|
||||
[
|
||||
"el-icon-delete-solid",
|
||||
"el-icon-delete",
|
||||
"el-icon-s-tools",
|
||||
"el-icon-setting",
|
||||
"el-icon-user-solid",
|
||||
"el-icon-user",
|
||||
"el-icon-phone",
|
||||
"el-icon-phone-outline",
|
||||
"el-icon-more",
|
||||
"el-icon-more-outline",
|
||||
"el-icon-star-on",
|
||||
"el-icon-star-off",
|
||||
"el-icon-s-goods",
|
||||
"el-icon-goods",
|
||||
"el-icon-warning",
|
||||
"el-icon-warning-outline",
|
||||
"el-icon-question",
|
||||
"el-icon-info",
|
||||
"el-icon-remove",
|
||||
"el-icon-circle-plus",
|
||||
"el-icon-success",
|
||||
"el-icon-error",
|
||||
"el-icon-zoom-in",
|
||||
"el-icon-zoom-out",
|
||||
"el-icon-remove-outline",
|
||||
"el-icon-circle-plus-outline",
|
||||
"el-icon-circle-check",
|
||||
"el-icon-circle-close",
|
||||
"el-icon-s-help",
|
||||
"el-icon-help",
|
||||
"el-icon-minus",
|
||||
"el-icon-plus",
|
||||
"el-icon-check",
|
||||
"el-icon-close",
|
||||
"el-icon-picture",
|
||||
"el-icon-picture-outline",
|
||||
"el-icon-picture-outline-round",
|
||||
"el-icon-upload",
|
||||
"el-icon-upload2",
|
||||
"el-icon-download",
|
||||
"el-icon-camera-solid",
|
||||
"el-icon-camera",
|
||||
"el-icon-video-camera-solid",
|
||||
"el-icon-video-camera",
|
||||
"el-icon-message-solid",
|
||||
"el-icon-bell",
|
||||
"el-icon-s-cooperation",
|
||||
"el-icon-s-order",
|
||||
"el-icon-s-platform",
|
||||
"el-icon-s-fold",
|
||||
"el-icon-s-unfold",
|
||||
"el-icon-s-operation",
|
||||
"el-icon-s-promotion",
|
||||
"el-icon-s-home",
|
||||
"el-icon-s-release",
|
||||
"el-icon-s-ticket",
|
||||
"el-icon-s-management",
|
||||
"el-icon-s-open",
|
||||
"el-icon-s-shop",
|
||||
"el-icon-s-marketing",
|
||||
"el-icon-s-flag",
|
||||
"el-icon-s-comment",
|
||||
"el-icon-s-finance",
|
||||
"el-icon-s-claim",
|
||||
"el-icon-s-custom",
|
||||
"el-icon-s-opportunity",
|
||||
"el-icon-s-data",
|
||||
"el-icon-s-check",
|
||||
"el-icon-s-grid",
|
||||
"el-icon-menu",
|
||||
"el-icon-share",
|
||||
"el-icon-d-caret",
|
||||
"el-icon-caret-left",
|
||||
"el-icon-caret-right",
|
||||
"el-icon-caret-bottom",
|
||||
"el-icon-caret-top",
|
||||
"el-icon-bottom-left",
|
||||
"el-icon-bottom-right",
|
||||
"el-icon-back",
|
||||
"el-icon-right",
|
||||
"el-icon-bottom",
|
||||
"el-icon-top",
|
||||
"el-icon-top-left",
|
||||
"el-icon-top-right",
|
||||
"el-icon-arrow-left",
|
||||
"el-icon-arrow-right",
|
||||
"el-icon-arrow-down",
|
||||
"el-icon-arrow-up",
|
||||
"el-icon-d-arrow-left",
|
||||
"el-icon-d-arrow-right",
|
||||
"el-icon-video-pause",
|
||||
"el-icon-video-play",
|
||||
"el-icon-refresh",
|
||||
"el-icon-refresh-right",
|
||||
"el-icon-refresh-left",
|
||||
"el-icon-finished",
|
||||
"el-icon-sort",
|
||||
"el-icon-sort-up",
|
||||
"el-icon-sort-down",
|
||||
"el-icon-rank",
|
||||
"el-icon-loading",
|
||||
"el-icon-view",
|
||||
"el-icon-c-scale-to-original",
|
||||
"el-icon-date",
|
||||
"el-icon-edit",
|
||||
"el-icon-edit-outline",
|
||||
"el-icon-folder",
|
||||
"el-icon-folder-opened",
|
||||
"el-icon-folder-add",
|
||||
"el-icon-folder-remove",
|
||||
"el-icon-folder-delete",
|
||||
"el-icon-folder-checked",
|
||||
"el-icon-tickets",
|
||||
"el-icon-document-remove",
|
||||
"el-icon-document-delete",
|
||||
"el-icon-document-copy",
|
||||
"el-icon-document-checked",
|
||||
"el-icon-document",
|
||||
"el-icon-document-add",
|
||||
"el-icon-printer",
|
||||
"el-icon-paperclip",
|
||||
"el-icon-takeaway-box",
|
||||
"el-icon-search",
|
||||
"el-icon-monitor",
|
||||
"el-icon-attract",
|
||||
"el-icon-mobile",
|
||||
"el-icon-scissors",
|
||||
"el-icon-umbrella",
|
||||
"el-icon-headset",
|
||||
"el-icon-brush",
|
||||
"el-icon-mouse",
|
||||
"el-icon-coordinate",
|
||||
"el-icon-magic-stick",
|
||||
"el-icon-reading",
|
||||
"el-icon-data-line",
|
||||
"el-icon-data-board",
|
||||
"el-icon-pie-chart",
|
||||
"el-icon-data-analysis",
|
||||
"el-icon-collection-tag",
|
||||
"el-icon-film",
|
||||
"el-icon-suitcase",
|
||||
"el-icon-suitcase-1",
|
||||
"el-icon-receiving",
|
||||
"el-icon-collection",
|
||||
"el-icon-files",
|
||||
"el-icon-notebook-1",
|
||||
"el-icon-notebook-2",
|
||||
"el-icon-toilet-paper",
|
||||
"el-icon-office-building",
|
||||
"el-icon-school",
|
||||
"el-icon-table-lamp",
|
||||
"el-icon-house",
|
||||
"el-icon-no-smoking",
|
||||
"el-icon-smoking",
|
||||
"el-icon-shopping-cart-full",
|
||||
"el-icon-shopping-cart-1",
|
||||
"el-icon-shopping-cart-2",
|
||||
"el-icon-shopping-bag-1",
|
||||
"el-icon-shopping-bag-2",
|
||||
"el-icon-sold-out",
|
||||
"el-icon-sell",
|
||||
"el-icon-present",
|
||||
"el-icon-box",
|
||||
"el-icon-bank-card",
|
||||
"el-icon-money",
|
||||
"el-icon-coin",
|
||||
"el-icon-wallet",
|
||||
"el-icon-discount",
|
||||
"el-icon-price-tag",
|
||||
"el-icon-news",
|
||||
"el-icon-guide",
|
||||
"el-icon-male",
|
||||
"el-icon-female",
|
||||
"el-icon-thumb",
|
||||
"el-icon-cpu",
|
||||
"el-icon-link",
|
||||
"el-icon-connection",
|
||||
"el-icon-open",
|
||||
"el-icon-turn-off",
|
||||
"el-icon-set-up",
|
||||
"el-icon-chat-round",
|
||||
"el-icon-chat-line-round",
|
||||
"el-icon-chat-square",
|
||||
"el-icon-chat-dot-round",
|
||||
"el-icon-chat-dot-square",
|
||||
"el-icon-chat-line-square",
|
||||
"el-icon-message",
|
||||
"el-icon-postcard",
|
||||
"el-icon-position",
|
||||
"el-icon-turn-off-microphone",
|
||||
"el-icon-microphone",
|
||||
"el-icon-close-notification",
|
||||
"el-icon-bangzhu",
|
||||
"el-icon-time",
|
||||
"el-icon-odometer",
|
||||
"el-icon-crop",
|
||||
"el-icon-aim",
|
||||
"el-icon-switch-button",
|
||||
"el-icon-full-screen",
|
||||
"el-icon-copy-document",
|
||||
"el-icon-mic",
|
||||
"el-icon-stopwatch",
|
||||
"el-icon-medal-1",
|
||||
"el-icon-medal",
|
||||
"el-icon-trophy",
|
||||
"el-icon-trophy-1",
|
||||
"el-icon-first-aid-kit",
|
||||
"el-icon-discover",
|
||||
"el-icon-place",
|
||||
"el-icon-location",
|
||||
"el-icon-location-outline",
|
||||
"el-icon-location-information",
|
||||
"el-icon-add-location",
|
||||
"el-icon-delete-location",
|
||||
"el-icon-map-location",
|
||||
"el-icon-alarm-clock",
|
||||
"el-icon-timer",
|
||||
"el-icon-watch-1",
|
||||
"el-icon-watch",
|
||||
"el-icon-lock",
|
||||
"el-icon-unlock",
|
||||
"el-icon-key",
|
||||
"el-icon-service",
|
||||
"el-icon-mobile-phone",
|
||||
"el-icon-bicycle",
|
||||
"el-icon-truck",
|
||||
"el-icon-ship",
|
||||
"el-icon-basketball",
|
||||
"el-icon-football",
|
||||
"el-icon-soccer",
|
||||
"el-icon-baseball",
|
||||
"el-icon-wind-power",
|
||||
"el-icon-light-rain",
|
||||
"el-icon-lightning",
|
||||
"el-icon-heavy-rain",
|
||||
"el-icon-sunrise",
|
||||
"el-icon-sunrise-1",
|
||||
"el-icon-sunset",
|
||||
"el-icon-sunny",
|
||||
"el-icon-cloudy",
|
||||
"el-icon-partly-cloudy",
|
||||
"el-icon-cloudy-and-sunny",
|
||||
"el-icon-moon",
|
||||
"el-icon-moon-night",
|
||||
"el-icon-dish",
|
||||
"el-icon-dish-1",
|
||||
"el-icon-food",
|
||||
"el-icon-chicken",
|
||||
"el-icon-fork-spoon",
|
||||
"el-icon-knife-fork",
|
||||
"el-icon-burger",
|
||||
"el-icon-tableware",
|
||||
"el-icon-sugar",
|
||||
"el-icon-dessert",
|
||||
"el-icon-ice-cream",
|
||||
"el-icon-hot-water",
|
||||
"el-icon-water-cup",
|
||||
"el-icon-coffee-cup",
|
||||
"el-icon-cold-drink",
|
||||
"el-icon-goblet",
|
||||
"el-icon-goblet-full",
|
||||
"el-icon-goblet-square",
|
||||
"el-icon-goblet-square-full",
|
||||
"el-icon-refrigerator",
|
||||
"el-icon-grape",
|
||||
"el-icon-watermelon",
|
||||
"el-icon-cherry",
|
||||
"el-icon-apple",
|
||||
"el-icon-pear",
|
||||
"el-icon-orange",
|
||||
"el-icon-coffee",
|
||||
"el-icon-ice-tea",
|
||||
"el-icon-ice-drink",
|
||||
"el-icon-milk-tea",
|
||||
"el-icon-potato-strips",
|
||||
"el-icon-lollipop",
|
||||
"el-icon-ice-cream-square",
|
||||
"el-icon-ice-cream-round"
|
||||
]
|
||||
@@ -0,0 +1,104 @@
|
||||
<template>
|
||||
<el-popover width="510" v-model="showDropdown" @show="onDropdownShow">
|
||||
<div class="icon-select-dropdown">
|
||||
<el-row type="flex" style="flex-wrap: wrap">
|
||||
<el-col :span="3" v-for="icon in getIconList" :key="icon" class="icon-item"
|
||||
:class="{active: (value === icon)}" @click.native="onIconClick(icon)">
|
||||
<i :class="icon" />
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row type="flex" justify="space-between">
|
||||
<el-button type="text" @click="onClearClick" style="margin-left: 10px;">清空</el-button>
|
||||
<el-pagination
|
||||
:current-page.sync="currentPage"
|
||||
:page-size="pageSize"
|
||||
layout="prev, pager, next"
|
||||
:total="getIconCount">
|
||||
</el-pagination>
|
||||
</el-row>
|
||||
</div>
|
||||
<div slot="reference" class="icon-select"
|
||||
:style="{width: height + 'px', height: height + 'px', 'line-height': height + 'px', 'font-size': height * 0.5 + 'px'}">
|
||||
<i :class="value" />
|
||||
</div>
|
||||
</el-popover>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import iconList from './icon.json';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
/**
|
||||
* 绑定字段
|
||||
*/
|
||||
value: String,
|
||||
/**
|
||||
* 组件高度,单位px
|
||||
*/
|
||||
height: {
|
||||
type: Number,
|
||||
default: 45
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
showDropdown: false,
|
||||
currentPage: 1,
|
||||
pageSize: 32
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onIconClick (icon) {
|
||||
this.$emit('input', icon);
|
||||
this.showDropdown = false;
|
||||
},
|
||||
onClearClick () {
|
||||
this.$emit('input');
|
||||
this.showDropdown = false;
|
||||
},
|
||||
onDropdownShow () {
|
||||
this.currentPage = 1
|
||||
let pos = iconList.indexOf(this.value);
|
||||
if (pos >= 0) {
|
||||
this.currentPage += Math.floor(pos / this.pageSize);
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getIconCount () {
|
||||
return iconList.length;
|
||||
},
|
||||
getIconList () {
|
||||
let beginPos = (this.currentPage - 1) * this.pageSize;
|
||||
let endPos = beginPos + this.pageSize;
|
||||
return iconList.slice(beginPos, endPos);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.icon-select {
|
||||
text-align: center;
|
||||
color: #5F6266;
|
||||
border: 1px solid #DCDFE6;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.icon-item {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
color: #5F6266;
|
||||
border-radius: 3px;
|
||||
border: 1px solid #DCDFE6;
|
||||
margin: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.active {
|
||||
color: #EF5E1C;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,227 @@
|
||||
<template>
|
||||
<div class="el-input el-date-editor el-range-editor el-input__inner el-input-number-range"
|
||||
:class="
|
||||
[
|
||||
inputSize ? 'el-range-editor--' + inputSize : '',
|
||||
focused ? 'is-active' : '',
|
||||
{
|
||||
'is-disabled': inputDisabled,
|
||||
'el-input--prefix': prefixIcon
|
||||
}
|
||||
]"
|
||||
@mouseenter="showClose = true"
|
||||
@mouseleave="showClose = false">
|
||||
<div class="el-input__icon el-range__icon" :class="prefixIcon">
|
||||
<slot name="prepend"></slot>
|
||||
</div>
|
||||
<input
|
||||
autocomplete="off"
|
||||
:placeholder="startPlaceholder"
|
||||
:value="userInput && userInput[0]"
|
||||
:disabled="inputDisabled"
|
||||
:readonly="readonly"
|
||||
:name="name && name[0]"
|
||||
@input="handleStartInput"
|
||||
@change="handleStartChange"
|
||||
@focus="focused = true"
|
||||
@blur="focused = false"
|
||||
class="el-range-input">
|
||||
<slot name="range-separator">
|
||||
<span class="el-range-separator">{{ rangeSeparator }}</span>
|
||||
</slot>
|
||||
<input
|
||||
autocomplete="off"
|
||||
:placeholder="endPlaceholder"
|
||||
:value="userInput && userInput[1]"
|
||||
:disabled="inputDisabled"
|
||||
:readonly="readonly"
|
||||
:name="name && name[1]"
|
||||
@input="handleEndInput"
|
||||
@change="handleEndChange"
|
||||
@focus="focused = true"
|
||||
@blur="focused = false"
|
||||
class="el-range-input">
|
||||
<i class="el-input__icon el-range__close-icon"
|
||||
:class="[showClear ? 'el-icon-circle-close' : '']"
|
||||
@click="handleClickClear">
|
||||
</i>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import emitter from 'element-ui/src/mixins/emitter';
|
||||
|
||||
function isNumber (val) {
|
||||
var regPos = /^\d+(\.\d+)?$/; // 非负浮点数
|
||||
var regNeg = /^(-(([0-9]+\.[0-9]*[1-9][0-9]*)|([0-9]*[1-9][0-9]*\.[0-9]+)|([0-9]*[1-9][0-9]*)))$/; // 负浮点数
|
||||
if (regPos.test(val) || regNeg.test(val)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
export default {
|
||||
name: 'InputNumberRange',
|
||||
componentName: 'InputNumberRange',
|
||||
mixins: [emitter],
|
||||
props: {
|
||||
/**
|
||||
* 绑定字段
|
||||
*/
|
||||
value: {
|
||||
type: Array,
|
||||
default: function () {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 组件大小(medium / small / mini)
|
||||
*/
|
||||
size: String,
|
||||
/**
|
||||
* 禁用
|
||||
*/
|
||||
disabled: Boolean,
|
||||
/**
|
||||
* 完全只读
|
||||
*/
|
||||
readonly: Boolean,
|
||||
/**
|
||||
* 是否显示清除按钮
|
||||
*/
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
/**
|
||||
* 自定义头部图标的类名
|
||||
*/
|
||||
prefixIcon: String,
|
||||
/**
|
||||
* 范围选择时最小值的占位内容
|
||||
*/
|
||||
startPlaceholder: String,
|
||||
/**
|
||||
* 范围选择时最大值的占位内容
|
||||
*/
|
||||
endPlaceholder: String,
|
||||
/**
|
||||
* 原生属性
|
||||
*/
|
||||
name: {
|
||||
default: ''
|
||||
},
|
||||
/**
|
||||
* 选择范围时的分隔符
|
||||
*/
|
||||
rangeSeparator: {
|
||||
type: String,
|
||||
default: '-'
|
||||
},
|
||||
validateEvent: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
hovering: false,
|
||||
focused: false,
|
||||
userInput: this.value,
|
||||
showClose: false
|
||||
};
|
||||
},
|
||||
inject: {
|
||||
elForm: {
|
||||
default: ''
|
||||
},
|
||||
elFormItem: {
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
_elFormItemSize () {
|
||||
return (this.elFormItem || {}).elFormItemSize;
|
||||
},
|
||||
inputSize () {
|
||||
let temp = this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
|
||||
return temp;
|
||||
},
|
||||
inputDisabled () {
|
||||
return this.disabled || (this.elForm || {}).disabled;
|
||||
},
|
||||
showClear () {
|
||||
let temp = this.clearable && !this.inputDisabled && !this.readonly && this.showClose &&
|
||||
this.userInput != null && Array.isArray(this.userInput) && this.userInput.length > 0 &&
|
||||
(this.userInput[0] != null || this.userInput[1] != null);
|
||||
return temp;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleStartInput (event) {
|
||||
if (this.userInput) {
|
||||
this.userInput = [event.target.value, this.userInput[1]];
|
||||
} else {
|
||||
this.userInput = [event.target.value, null];
|
||||
}
|
||||
},
|
||||
|
||||
handleEndInput (event) {
|
||||
if (this.userInput) {
|
||||
this.userInput = [this.userInput[0], event.target.value];
|
||||
} else {
|
||||
this.userInput = [null, event.target.value];
|
||||
}
|
||||
},
|
||||
handleStartChange (event) {
|
||||
let value = this.userInput && this.userInput[0];
|
||||
value = isNumber(value) ? value : null;
|
||||
value = value ? Number.parseFloat(value) : null;
|
||||
if (this.userInput) {
|
||||
this.userInput[0] = value;
|
||||
} else {
|
||||
this.userInput = [value, null];
|
||||
}
|
||||
event.srcElement.value = value;
|
||||
this.emitInput(this.userInput);
|
||||
},
|
||||
handleEndChange (event) {
|
||||
let value = this.userInput && this.userInput[1];
|
||||
value = isNumber(value) ? value : null;
|
||||
value = value ? Number.parseFloat(value) : null;
|
||||
if (this.userInput) {
|
||||
this.userInput[1] = value;
|
||||
} else {
|
||||
this.userInput = [null, value];
|
||||
}
|
||||
event.srcElement.value = value;
|
||||
this.emitInput(this.userInput);
|
||||
},
|
||||
handleClickClear () {
|
||||
this.userInput = undefined;
|
||||
this.emitInput(this.userInput);
|
||||
},
|
||||
valueEquals (val, oldVal) {
|
||||
return JSON.stringify(val) === JSON.stringify(oldVal);
|
||||
},
|
||||
emitInput (values) {
|
||||
this.$emit('input', values);
|
||||
this.$emit('change', values);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler: function (val, oldVal) {
|
||||
this.userInput = val;
|
||||
if (!this.valueEquals(val, oldVal) && this.validateEvent) {
|
||||
this.dispatch('ElFormItem', 'el.form.change', val);
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -0,0 +1,44 @@
|
||||
<template>
|
||||
<el-progress v-bind="$attrs" :percentage="getPercentage" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Progress',
|
||||
props: {
|
||||
/**
|
||||
* 组件最小值
|
||||
*/
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
/**
|
||||
* 组件最大值
|
||||
*/
|
||||
max: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
/**
|
||||
* 组件当前值
|
||||
*/
|
||||
value: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getPercentage () {
|
||||
let value = Math.min(this.max, Math.max(this.min, this.value));
|
||||
value = value - this.min;
|
||||
if ((this.max - this.min) === 0) {
|
||||
value = 0;
|
||||
} else {
|
||||
value = ((value * 100) / (this.max - this.min));
|
||||
}
|
||||
return Number.isInteger(value) ? value : parseInt(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<div class="uditor-class" ref="editor"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WEditor from 'wangeditor';
|
||||
|
||||
const defaultConfigs = {
|
||||
uploadImgServer: undefined,
|
||||
uploadFileName: 'imageFile',
|
||||
uploadImgMaxSize: 1 * 1024 * 1024,
|
||||
uploadImgShowBase64: true,
|
||||
uploadImgMaxLength: 5,
|
||||
uploadImgParams: undefined,
|
||||
uploadImgParamsWithUrl: true,
|
||||
withCredentials: true,
|
||||
uploadImgTimeout: 5000,
|
||||
uploadImgHeaders: undefined,
|
||||
uploadImgHooks: undefined,
|
||||
zIndex: 0,
|
||||
lang: undefined,
|
||||
pasteFilterStyle: true,
|
||||
pasteIgnoreImg: false,
|
||||
onchangeTimeout: 10,
|
||||
menus: [
|
||||
// 标题
|
||||
'head',
|
||||
// 粗体
|
||||
'bold',
|
||||
// 字号
|
||||
'fontSize',
|
||||
// 字体
|
||||
'fontName',
|
||||
// 斜体
|
||||
'italic',
|
||||
// 下划线
|
||||
'underline',
|
||||
// 删除线
|
||||
'strikeThrough',
|
||||
// 文字颜色
|
||||
'foreColor',
|
||||
// 背景颜色
|
||||
'backColor',
|
||||
// 插入链接
|
||||
'link',
|
||||
// 列表
|
||||
'list',
|
||||
// 对齐方式
|
||||
'justify',
|
||||
// 引用
|
||||
'quote',
|
||||
// 插入图片
|
||||
'image',
|
||||
// 撤销
|
||||
'undo',
|
||||
// 重复
|
||||
'redo'
|
||||
]
|
||||
}
|
||||
|
||||
export default {
|
||||
props: {
|
||||
/**
|
||||
* 绑定字段
|
||||
*/
|
||||
value: {
|
||||
type: String
|
||||
},
|
||||
/**
|
||||
* 配置项,详情请参考wangEditor文档
|
||||
*/
|
||||
config: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return defaultConfigs;
|
||||
}
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
editor: null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getHtml () {
|
||||
return this.editor ? this.editor.txt.html() : undefined;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
getConfigs () {
|
||||
return {...this.config, ...defaultConfigs};
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
this.editor = new WEditor(this.$refs.editor);
|
||||
this.editor.customConfig = {...this.getConfigs};
|
||||
this.editor.customConfig.pasteTextHandle = (content) => {
|
||||
// content 即粘贴过来的内容(html 或 纯文本),可进行自定义处理然后返回
|
||||
return content;
|
||||
}
|
||||
this.editor.customConfig.linkImgCallback = (url) => {
|
||||
}
|
||||
this.editor.customConfig.linkCheck = (text, link) => {
|
||||
return true // 返回 true 表示校验成功
|
||||
// return '验证失败' // 返回字符串,即校验失败的提示信息
|
||||
}
|
||||
|
||||
this.editor.customConfig.linkImgCheck = (src) => {
|
||||
return true // 返回 true 表示校验成功
|
||||
// return '验证失败' // 返回字符串,即校验失败的提示信息
|
||||
}
|
||||
// 失去焦点后更新数据
|
||||
this.editor.customConfig.onblur = (html) => {
|
||||
this.$emit('input', html);
|
||||
}
|
||||
|
||||
this.editor.create();
|
||||
this.editor.txt.html(this.value);
|
||||
},
|
||||
watch: {
|
||||
value: {
|
||||
handler (newValue) {
|
||||
if (this.editor) this.editor.txt.html(this.value);
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<el-table-column v-bind="$attrs">
|
||||
<template slot-scope="scope">
|
||||
<orange-progress :stroke-width="strokeWidth" :type="type" :text-inside="textInside" :status="status" :color="color"
|
||||
:width="width" :show-text="showText" :min="getMinValue(scope.row)" :max="getMaxValue(scope.row)"
|
||||
:value="getValue(scope.row)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Progress from '@/components/Progress/index.vue';
|
||||
|
||||
export default {
|
||||
name: 'TableProgressColumn',
|
||||
components: {
|
||||
'orange-progress': Progress
|
||||
},
|
||||
props: {
|
||||
/**
|
||||
* 固定值最小值
|
||||
*/
|
||||
min: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
/**
|
||||
* 固定值最大值
|
||||
*/
|
||||
max: {
|
||||
type: Number,
|
||||
default: 100
|
||||
},
|
||||
/**
|
||||
* 固定值当前值
|
||||
*/
|
||||
value: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
/**
|
||||
* 表格最小值字段名
|
||||
*/
|
||||
minColumn: {
|
||||
type: String
|
||||
},
|
||||
/**
|
||||
* 表格最大值字段名
|
||||
*/
|
||||
maxColumn: {
|
||||
type: String
|
||||
},
|
||||
/**
|
||||
* 表格当前值字段名
|
||||
*/
|
||||
valueColumn: {
|
||||
type: String
|
||||
},
|
||||
/**
|
||||
* 进度条的宽度,单位 px
|
||||
*/
|
||||
strokeWidth: {
|
||||
type: Number,
|
||||
default: 16
|
||||
},
|
||||
/**
|
||||
* 进度条类型(line/circle/dashboard)
|
||||
*/
|
||||
type: {
|
||||
type: String,
|
||||
default: 'line'
|
||||
},
|
||||
/**
|
||||
* 进度条显示文字内置在进度条内(只在 type=line 时可用)
|
||||
*/
|
||||
textInside: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
/**
|
||||
* 进度条当前状态(success/exception/warning)
|
||||
*/
|
||||
status: {
|
||||
type: String
|
||||
},
|
||||
/**
|
||||
* 进度条背景色(会覆盖 status 状态颜色)
|
||||
*/
|
||||
color: {
|
||||
type: [String, Function, Array]
|
||||
},
|
||||
/**
|
||||
* 环形进度条画布宽度(只在 type 为 circle 或 dashboard 时可用)
|
||||
*/
|
||||
width: {
|
||||
type: Number,
|
||||
default: 126
|
||||
},
|
||||
/**
|
||||
* 是否显示进度条文字内容
|
||||
*/
|
||||
showText: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getValue (row) {
|
||||
return this.valueColumn ? row[this.valueColumn] : this.value;
|
||||
},
|
||||
getMinValue (row) {
|
||||
return this.minColumn ? row[this.minColumn] : this.min;
|
||||
},
|
||||
getMaxValue (row) {
|
||||
return this.maxColumn ? row[this.maxColumn] : this.max;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,290 @@
|
||||
<template>
|
||||
<el-popover ref="popover" placement="bottom-start" trigger="click" popper-class="tree-select-popover"
|
||||
:width="width" @show="onShowPopover">
|
||||
<el-scrollbar :style="{'height': this.height, 'min-width': this.width}" ref="scrollbar">
|
||||
<el-tree ref="dropdownTree" :props="getTreeProps" :highlightCurrent="highlightCurrent" :nodeKey="getDataProps.value"
|
||||
:defaultExpandAll="defaultExpandAll" :expandOnClickNode="expandOnClickNode" :checkOnClickNode="checkOnClickNode"
|
||||
:autoExpandParent="autoExpandParent" :defaultExpandedKeys="defaultExpandedKeys" :showCheckbox="showCheckbox"
|
||||
:checkStrictly="checkStrictly" :defaultCheckedKeys="defaultCheckedKeys" :currentNodeKey="getCurrentNodeKey"
|
||||
:accordion="accordion" :indent="indent" :iconClass="iconClass" :load="loadChildrenNodes" lazy :show-checkbox="multiple"
|
||||
@node-click="onTreeNodeClick" @check="onTreeNodeCheck">
|
||||
<span :style="getNodeStyle(data)" slot-scope="{ node, data }">{{data[getDataProps.label]}}</span>
|
||||
</el-tree>
|
||||
</el-scrollbar>
|
||||
<el-select slot="reference" v-model="selectKeys" :multiple="multiple" :disabled="false" :size="size"
|
||||
:clearable="clearable" :collapseTags="collapseTags" :placeholder="placeholder" popper-class="select-tree-popper"
|
||||
@clear="onClear" @remove-tag="onClear">
|
||||
<el-option v-for="item in selectNodes" :key="item[getDataProps.value]"
|
||||
:value="item[getDataProps.value]" :label="item[getDataProps.label]" />
|
||||
</el-select>
|
||||
</el-popover>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { findTreeNode } from '@/utils';
|
||||
|
||||
export default {
|
||||
name: 'TreeSelect',
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number]
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
size: {
|
||||
type: String
|
||||
},
|
||||
height: {
|
||||
type: String,
|
||||
default: '200px'
|
||||
},
|
||||
width: {
|
||||
type: String,
|
||||
default: '300px'
|
||||
},
|
||||
activeColor: {
|
||||
type: String,
|
||||
default: '#EF5E1C'
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
collapseTags: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
placeholder: {
|
||||
type: String
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
loadingText: {
|
||||
type: String,
|
||||
default: '加载中'
|
||||
},
|
||||
// 树属性
|
||||
data: {
|
||||
type: Array
|
||||
},
|
||||
props: {
|
||||
type: Object,
|
||||
default: () => {
|
||||
return {
|
||||
label: 'label',
|
||||
value: 'value',
|
||||
parentKey: 'parentId',
|
||||
children: 'children',
|
||||
disabled: 'disabled'
|
||||
}
|
||||
}
|
||||
},
|
||||
defaultExpandAll: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
expandOnClickNode: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
checkOnClickNode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
autoExpandParent: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
defaultExpandedKeys: {
|
||||
type: Array
|
||||
},
|
||||
checkStrictly: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
currentNodeKey: {
|
||||
type: [String, Number]
|
||||
},
|
||||
accordion: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
indent: {
|
||||
type: Number,
|
||||
default: 16
|
||||
},
|
||||
iconClass: {
|
||||
type: String
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
rootNode: undefined,
|
||||
rootResolve: undefined,
|
||||
allTreeNode: [],
|
||||
selectNodes: [],
|
||||
scrollTop: 0,
|
||||
selectKeys: undefined
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onShowPopover () {
|
||||
setTimeout(() => {
|
||||
this.$refs.scrollbar.wrap.scrollTop = this.scrollTop;
|
||||
// this.$refs.scrollbar.update();
|
||||
}, 20);
|
||||
if (!this.multiple) {
|
||||
this.$refs.dropdownTree.setCurrentKey(this.value);
|
||||
}
|
||||
},
|
||||
onClear () {
|
||||
this.$nextTick(() => {
|
||||
this.$emit('input', this.selectKeys);
|
||||
});
|
||||
},
|
||||
onTreeNodeClick (data, node) {
|
||||
this.$refs.popover.showPopper = false;
|
||||
if (!this.multiple) {
|
||||
this.scrollTop = this.$refs.scrollbar.wrap.scrollTop;
|
||||
this.$emit('input', data[this.getDataProps.value]);
|
||||
this.$emit('change', data[this.getDataProps.value]);
|
||||
}
|
||||
},
|
||||
onTreeNodeCheck (data, {checkedNodes, checkedKeys, halfCheckedNodes, halfCheckedKeys}) {
|
||||
this.scrollTop = this.$refs.scrollbar.wrap.scrollTop
|
||||
this.$emit('input', checkedKeys);
|
||||
this.$emit('change', checkedKeys);
|
||||
},
|
||||
parseNode (node) {
|
||||
if (Array.isArray(node) && node.length > 0) {
|
||||
node.forEach((item) => {
|
||||
item['__node_is_leaf__'] = !(Array.isArray(item[this.getDataProps.children]) && item[this.getDataProps.children].length > 0);
|
||||
});
|
||||
return node;
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
loadChildrenNodes (node, resolve) {
|
||||
if (node.level === 0) {
|
||||
this.rootNode = node;
|
||||
this.rootResolve = resolve;
|
||||
return resolve(this.parseNode(this.allTreeNode));
|
||||
} else {
|
||||
return resolve(this.parseNode(node.data[this.getDataProps.children]));
|
||||
}
|
||||
},
|
||||
getNodeStyle (data) {
|
||||
if (!this.multiple && (this.selectNodes[0] || {})[this.getDataProps.value] === data[this.getDataProps.value]) {
|
||||
return {
|
||||
color: this.activeColor,
|
||||
'font-weight': 700
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showCheckbox () {
|
||||
return this.multiple;
|
||||
},
|
||||
getCurrentNodeKey () {
|
||||
if (!this.multiple && Array.isArray(this.selectNodes) && this.selectNodes.length > 0) {
|
||||
return this.selectNodes[0][this.getDataProps.value];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
highlightCurrent () {
|
||||
return this.multiple;
|
||||
},
|
||||
defaultCheckedKeys () {
|
||||
return this.multiple ? this.selectNodes : undefined
|
||||
},
|
||||
getDataProps () {
|
||||
return {
|
||||
label: this.props.label || 'label',
|
||||
value: this.props.value || 'value',
|
||||
parentKey: this.props.parentKey || 'parentId',
|
||||
children: this.props.children || 'children',
|
||||
disabled: this.props.disabled || 'disabled'
|
||||
}
|
||||
},
|
||||
getTreeProps () {
|
||||
return {
|
||||
label: this.getDataProps.label,
|
||||
children: '__children_list__',
|
||||
disabled: this.getDataProps.disabled,
|
||||
isLeaf: '__node_is_leaf__'
|
||||
};
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
data: {
|
||||
handler (newValue, oldValue) {
|
||||
this.allTreeNode = newValue;
|
||||
if (this.rootNode != null && this.rootResolve != null) {
|
||||
this.rootNode.childNodes = [];
|
||||
this.loadChildrenNodes(this.rootNode, this.rootResolve);
|
||||
}
|
||||
this.selectNodes = [];
|
||||
if (this.multiple) {
|
||||
if (Array.isArray(this.value)) {
|
||||
this.value.forEach((item) => {
|
||||
let data = findTreeNode(this.allTreeNode, item, this.getDataProps.value, this.getDataProps.children);
|
||||
if (data) this.selectNodes.push(data);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
let data = findTreeNode(this.allTreeNode, this.value, this.getDataProps.value, this.getDataProps.children);
|
||||
if (data) this.selectNodes.push(data);
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
value: {
|
||||
handler (newValue) {
|
||||
this.selectNodes = [];
|
||||
if (Array.isArray(newValue)) {
|
||||
newValue.forEach((item) => {
|
||||
let data = findTreeNode(this.allTreeNode, item, this.getDataProps.value, this.getDataProps.children);
|
||||
if (data) this.selectNodes.push(data);
|
||||
});
|
||||
this.selectKeys = newValue;
|
||||
} else {
|
||||
let data = findTreeNode(this.allTreeNode, newValue, this.getDataProps.value, this.getDataProps.children);
|
||||
if (data) this.selectNodes.push(data);
|
||||
this.selectKeys = newValue;
|
||||
}
|
||||
if (this.$refs.dropdownTree) {
|
||||
this.multiple ? this.$refs.dropdownTree.setCheckedKeys(newValue) : this.$refs.dropdownTree.setCurrentKey(newValue);
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.select-tree-popper {
|
||||
display: none;
|
||||
}
|
||||
.tree-select-popover {
|
||||
padding: 6px 0px;
|
||||
}
|
||||
.tree-select-popover .popper__arrow {
|
||||
left: 35px!important;
|
||||
}
|
||||
.tree-select-popover .el-tree .el-tree-node__content {
|
||||
height: 34px;
|
||||
line-height: 34px;
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,268 @@
|
||||
<template>
|
||||
<div
|
||||
style="position: relative"
|
||||
>
|
||||
<div class="verify-img-out">
|
||||
<div
|
||||
class="verify-img-panel"
|
||||
:style="{'width': setSize.imgWidth,
|
||||
'height': setSize.imgHeight,
|
||||
'background-size' : setSize.imgWidth + ' '+ setSize.imgHeight,
|
||||
'margin-bottom': vSpace + 'px'}"
|
||||
>
|
||||
<div v-show="showRefresh" class="verify-refresh" style="z-index:3" @click="refresh">
|
||||
<i class="iconfont icon-refresh" />
|
||||
</div>
|
||||
<img
|
||||
ref="canvas"
|
||||
:src="pointBackImgBase?('data:image/png;base64,'+pointBackImgBase):defaultImg"
|
||||
alt=""
|
||||
style="width:100%;height:100%;display:block"
|
||||
@click="bindingClick?canvasClick($event):undefined"
|
||||
>
|
||||
|
||||
<div
|
||||
v-for="(tempPoint, index) in tempPoints"
|
||||
:key="index"
|
||||
class="point-area"
|
||||
:style="{
|
||||
'background-color':'#1abd6c',
|
||||
color:'#fff',
|
||||
'z-index':9999,
|
||||
width:'20px',
|
||||
height:'20px',
|
||||
'text-align':'center',
|
||||
'line-height':'20px',
|
||||
'border-radius': '50%',
|
||||
position:'absolute',
|
||||
top:parseInt(tempPoint.y-10) + 'px',
|
||||
left:parseInt(tempPoint.x-10) + 'px'
|
||||
}"
|
||||
>
|
||||
{{ index + 1 }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 'height': this.barSize.height, -->
|
||||
<div
|
||||
class="verify-bar-area"
|
||||
:style="{'width': setSize.imgWidth,
|
||||
'color': this.barAreaColor,
|
||||
'border-color': this.barAreaBorderColor,
|
||||
'line-height':this.barSize.height}"
|
||||
>
|
||||
<span class="verify-msg">{{ text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script type="text/babel">
|
||||
/**
|
||||
* VerifyPoints
|
||||
* @description 点选
|
||||
* */
|
||||
import { resetSize, _code_chars, _code_color1, _code_color2 } from './../utils/util'
|
||||
import { aesEncrypt } from './../utils/ase'
|
||||
import { reqGet, reqCheck } from './../api/index'
|
||||
|
||||
export default {
|
||||
name: 'VerifyPoints',
|
||||
props: {
|
||||
// 弹出式pop,固定fixed
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'fixed'
|
||||
},
|
||||
captchaType: {
|
||||
type: String,
|
||||
},
|
||||
// 间隔
|
||||
vSpace: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
imgSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '155px'
|
||||
}
|
||||
}
|
||||
},
|
||||
barSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '40px'
|
||||
}
|
||||
}
|
||||
},
|
||||
defaultImg: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
secretKey: '', // 后端返回的ase加密秘钥
|
||||
checkNum: 3, // 默认需要点击的字数
|
||||
fontPos: [], // 选中的坐标信息
|
||||
checkPosArr: [], // 用户点击的坐标
|
||||
num: 1, // 点击的记数
|
||||
pointBackImgBase: '', // 后端获取到的背景图片
|
||||
poinTextList: [], // 后端返回的点击字体顺序
|
||||
backToken: '', // 后端返回的token值
|
||||
setSize: {
|
||||
imgHeight: 0,
|
||||
imgWidth: 0,
|
||||
barHeight: 0,
|
||||
barWidth: 0
|
||||
},
|
||||
tempPoints: [],
|
||||
text: '',
|
||||
barAreaColor: undefined,
|
||||
barAreaBorderColor: undefined,
|
||||
showRefresh: true,
|
||||
bindingClick: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
resetSize() {
|
||||
return resetSize
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// type变化则全面刷新
|
||||
type: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 禁止拖拽
|
||||
this.$el.onselectstart = function() {
|
||||
return false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
// 加载页面
|
||||
this.fontPos.splice(0, this.fontPos.length)
|
||||
this.checkPosArr.splice(0, this.checkPosArr.length)
|
||||
this.num = 1
|
||||
this.getPictrue()
|
||||
this.$nextTick(() => {
|
||||
this.setSize = this.resetSize(this) // 重新设置宽度高度
|
||||
this.$parent.$emit('ready', this)
|
||||
})
|
||||
},
|
||||
canvasClick(e) {
|
||||
this.checkPosArr.push(this.getMousePos(this.$refs.canvas, e))
|
||||
if (this.num == this.checkNum) {
|
||||
this.num = this.createPoint(this.getMousePos(this.$refs.canvas, e))
|
||||
// 按比例转换坐标值
|
||||
this.checkPosArr = this.pointTransfrom(this.checkPosArr, this.setSize)
|
||||
// 等创建坐标执行完
|
||||
setTimeout(() => {
|
||||
// var flag = this.comparePos(this.fontPos, this.checkPosArr);
|
||||
// 发送后端请求
|
||||
var captchaVerification = this.secretKey ? aesEncrypt(this.backToken + '---' + JSON.stringify(this.checkPosArr), this.secretKey) : this.backToken + '---' + JSON.stringify(this.checkPosArr)
|
||||
const data = {
|
||||
captchaType: this.captchaType,
|
||||
'pointJson': this.secretKey ? aesEncrypt(JSON.stringify(this.checkPosArr), this.secretKey) : JSON.stringify(this.checkPosArr),
|
||||
'token': this.backToken
|
||||
}
|
||||
reqCheck(data).then(res => {
|
||||
if (res.repCode == '0000') {
|
||||
this.barAreaColor = '#4cae4c'
|
||||
this.barAreaBorderColor = '#5cb85c'
|
||||
this.text = '验证成功'
|
||||
this.bindingClick = false
|
||||
if (this.mode == 'pop') {
|
||||
setTimeout(() => {
|
||||
this.$parent.clickShow = false
|
||||
this.refresh()
|
||||
}, 1500)
|
||||
}
|
||||
this.$parent.$emit('success', { captchaVerification })
|
||||
} else {
|
||||
this.$parent.$emit('error', this)
|
||||
this.barAreaColor = '#d9534f'
|
||||
this.barAreaBorderColor = '#d9534f'
|
||||
this.text = '验证失败'
|
||||
setTimeout(() => {
|
||||
this.refresh()
|
||||
}, 700)
|
||||
}
|
||||
})
|
||||
}, 400)
|
||||
}
|
||||
if (this.num < this.checkNum) {
|
||||
this.num = this.createPoint(this.getMousePos(this.$refs.canvas, e))
|
||||
}
|
||||
},
|
||||
|
||||
// 获取坐标
|
||||
getMousePos: function(obj, e) {
|
||||
var x = e.offsetX
|
||||
var y = e.offsetY
|
||||
return { x, y }
|
||||
},
|
||||
// 创建坐标点
|
||||
createPoint: function(pos) {
|
||||
this.tempPoints.push(Object.assign({}, pos))
|
||||
return ++this.num
|
||||
},
|
||||
refresh: function() {
|
||||
this.tempPoints.splice(0, this.tempPoints.length)
|
||||
this.barAreaColor = '#000'
|
||||
this.barAreaBorderColor = '#ddd'
|
||||
this.bindingClick = true
|
||||
this.fontPos.splice(0, this.fontPos.length)
|
||||
this.checkPosArr.splice(0, this.checkPosArr.length)
|
||||
this.num = 1
|
||||
this.getPictrue()
|
||||
this.text = '验证失败'
|
||||
this.showRefresh = true
|
||||
},
|
||||
|
||||
// 请求背景图片和验证图片
|
||||
getPictrue() {
|
||||
const data = {
|
||||
captchaType: this.captchaType,
|
||||
clientUid: localStorage.getItem('point'),
|
||||
ts: Date.now(), // 现在的时间戳
|
||||
}
|
||||
reqGet(data).then(res => {
|
||||
if (res.repCode == '0000') {
|
||||
this.pointBackImgBase = res.repData.originalImageBase64
|
||||
this.backToken = res.repData.token
|
||||
this.secretKey = res.repData.secretKey
|
||||
this.poinTextList = res.repData.wordList
|
||||
this.text = '请依次点击【' + this.poinTextList.join(',') + '】'
|
||||
} else {
|
||||
this.text = res.repMsg
|
||||
}
|
||||
|
||||
// 判断接口请求次数是否失效
|
||||
if (res.repCode == '6201') {
|
||||
this.pointBackImgBase = null
|
||||
}
|
||||
})
|
||||
},
|
||||
// 坐标转换函数
|
||||
pointTransfrom(pointArr, imgSize) {
|
||||
var newPointArr = pointArr.map(p => {
|
||||
const x = Math.round(310 * p.x / parseInt(imgSize.imgWidth))
|
||||
const y = Math.round(155 * p.y / parseInt(imgSize.imgHeight))
|
||||
return { x, y }
|
||||
})
|
||||
// console.log(newPointArr,"newPointArr");
|
||||
return newPointArr
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,381 @@
|
||||
<template>
|
||||
<div style="position: relative;">
|
||||
<div
|
||||
v-if="type === '2'"
|
||||
class="verify-img-out"
|
||||
:style="{height: (parseInt(setSize.imgHeight) + vSpace) + 'px'}"
|
||||
>
|
||||
<div
|
||||
class="verify-img-panel"
|
||||
:style="{
|
||||
width: setSize.imgWidth,
|
||||
height: setSize.imgHeight
|
||||
}"
|
||||
>
|
||||
<img :src="backImgBase?('data:image/png;base64,'+backImgBase):defaultImg" alt="" style="width:100%;height:100%;display:block">
|
||||
<div v-show="showRefresh" class="verify-refresh" @click="refresh"><i class="iconfont icon-refresh" />
|
||||
</div>
|
||||
<transition name="tips">
|
||||
<span v-if="tipWords" class="verify-tips" :class="passFlag ?'suc-bg':'err-bg'">{{ tipWords }}</span>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 公共部分 -->
|
||||
<div
|
||||
class="verify-bar-area"
|
||||
:style="{
|
||||
width: setSize.imgWidth,
|
||||
height: barSize.height,
|
||||
'line-height':barSize.height
|
||||
}"
|
||||
>
|
||||
<span class="verify-msg" v-text="text" />
|
||||
<div
|
||||
class="verify-left-bar"
|
||||
:style="{width: (leftBarWidth!==undefined)?leftBarWidth: barSize.height, height: barSize.height, 'border-color': leftBarBorderColor, transaction: transitionWidth}"
|
||||
>
|
||||
<span class="verify-msg" v-text="finishText" />
|
||||
<div
|
||||
class="verify-move-block"
|
||||
:style="{width: barSize.height, height: barSize.height, 'background-color': moveBlockBackgroundColor, left: moveBlockLeft, transition: transitionLeft}"
|
||||
@touchstart="start"
|
||||
@mousedown="start"
|
||||
>
|
||||
<i
|
||||
:class="['verify-icon iconfont', iconClass]"
|
||||
:style="{color: iconColor}"
|
||||
/>
|
||||
<div
|
||||
v-if="type === '2'"
|
||||
class="verify-sub-block"
|
||||
:style="{'width':Math.floor(parseInt(setSize.imgWidth)*47/310)+ 'px',
|
||||
'height': setSize.imgHeight,
|
||||
'top':'-' + (parseInt(setSize.imgHeight) + vSpace) + 'px',
|
||||
'background-size': setSize.imgWidth + ' ' + setSize.imgHeight,
|
||||
}"
|
||||
>
|
||||
<img :src="'data:image/png;base64,'+blockBackImgBase" alt="" style="width:100%;height:100%;display:block">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script type="text/babel">
|
||||
/**
|
||||
* VerifySlide
|
||||
* @description 滑块
|
||||
*/
|
||||
import { aesEncrypt } from './../utils/ase'
|
||||
import { resetSize } from './../utils/util'
|
||||
import { reqGet, reqCheck } from './../api/index'
|
||||
|
||||
// "captchaType":"blockPuzzle",
|
||||
export default {
|
||||
name: 'VerifySlide',
|
||||
props: {
|
||||
captchaType: {
|
||||
type: String,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: '1'
|
||||
},
|
||||
// 弹出式pop,固定fixed
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'fixed'
|
||||
},
|
||||
vSpace: {
|
||||
type: Number,
|
||||
default: 5
|
||||
},
|
||||
explain: {
|
||||
type: String,
|
||||
default: '向右滑动完成验证'
|
||||
},
|
||||
imgSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '155px'
|
||||
}
|
||||
}
|
||||
},
|
||||
blockSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '50px',
|
||||
height: '50px'
|
||||
}
|
||||
}
|
||||
},
|
||||
barSize: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {
|
||||
width: '310px',
|
||||
height: '40px'
|
||||
}
|
||||
}
|
||||
},
|
||||
defaultImg: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
secretKey: '', // 后端返回的加密秘钥 字段
|
||||
passFlag: '', // 是否通过的标识
|
||||
backImgBase: '', // 验证码背景图片
|
||||
blockBackImgBase: '', // 验证滑块的背景图片
|
||||
backToken: '', // 后端返回的唯一token值
|
||||
startMoveTime: '', // 移动开始的时间
|
||||
endMovetime: '', // 移动结束的时间
|
||||
tipsBackColor: '', // 提示词的背景颜色
|
||||
tipWords: '',
|
||||
text: '',
|
||||
finishText: '',
|
||||
setSize: {
|
||||
imgHeight: 0,
|
||||
imgWidth: 0,
|
||||
barHeight: 0,
|
||||
barWidth: 0
|
||||
},
|
||||
top: 0,
|
||||
left: 0,
|
||||
moveBlockLeft: undefined,
|
||||
leftBarWidth: undefined,
|
||||
// 移动中样式
|
||||
moveBlockBackgroundColor: undefined,
|
||||
leftBarBorderColor: '#ddd',
|
||||
iconColor: undefined,
|
||||
iconClass: 'icon-right',
|
||||
status: false, // 鼠标状态
|
||||
isEnd: false, // 是够验证完成
|
||||
showRefresh: true,
|
||||
transitionLeft: '',
|
||||
transitionWidth: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
barArea() {
|
||||
return this.$el.querySelector('.verify-bar-area')
|
||||
},
|
||||
resetSize() {
|
||||
return resetSize
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// type变化则全面刷新
|
||||
type: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 禁止拖拽
|
||||
this.$el.onselectstart = function() {
|
||||
return false
|
||||
}
|
||||
console.log(this.defaultImg)
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
this.text = this.explain
|
||||
this.getPictrue()
|
||||
this.$nextTick(() => {
|
||||
const setSize = this.resetSize(this) // 重新设置宽度高度
|
||||
for (const key in setSize) {
|
||||
this.$set(this.setSize, key, setSize[key])
|
||||
}
|
||||
this.$parent.$emit('ready', this)
|
||||
})
|
||||
|
||||
var _this = this
|
||||
|
||||
window.removeEventListener('touchmove', function(e) {
|
||||
_this.move(e)
|
||||
})
|
||||
window.removeEventListener('mousemove', function(e) {
|
||||
_this.move(e)
|
||||
})
|
||||
|
||||
// 鼠标松开
|
||||
window.removeEventListener('touchend', function() {
|
||||
_this.end()
|
||||
})
|
||||
window.removeEventListener('mouseup', function() {
|
||||
_this.end()
|
||||
})
|
||||
|
||||
window.addEventListener('touchmove', function(e) {
|
||||
_this.move(e)
|
||||
})
|
||||
window.addEventListener('mousemove', function(e) {
|
||||
_this.move(e)
|
||||
})
|
||||
|
||||
// 鼠标松开
|
||||
window.addEventListener('touchend', function() {
|
||||
_this.end()
|
||||
})
|
||||
window.addEventListener('mouseup', function() {
|
||||
_this.end()
|
||||
})
|
||||
},
|
||||
|
||||
// 鼠标按下
|
||||
start: function(e) {
|
||||
e = e || window.event
|
||||
if (!e.touches) { // 兼容PC端
|
||||
var x = e.clientX
|
||||
} else { // 兼容移动端
|
||||
var x = e.touches[0].pageX
|
||||
}
|
||||
this.startLeft = Math.floor(x - this.barArea.getBoundingClientRect().left)
|
||||
this.startMoveTime = +new Date() // 开始滑动的时间
|
||||
if (this.isEnd == false) {
|
||||
this.text = ''
|
||||
this.moveBlockBackgroundColor = '#337ab7'
|
||||
this.leftBarBorderColor = '#337AB7'
|
||||
this.iconColor = '#fff'
|
||||
e.stopPropagation()
|
||||
this.status = true
|
||||
}
|
||||
},
|
||||
// 鼠标移动
|
||||
move: function(e) {
|
||||
e = e || window.event
|
||||
if (this.status && this.isEnd == false) {
|
||||
if (!e.touches) { // 兼容PC端
|
||||
var x = e.clientX
|
||||
} else { // 兼容移动端
|
||||
var x = e.touches[0].pageX
|
||||
}
|
||||
var bar_area_left = this.barArea.getBoundingClientRect().left
|
||||
var move_block_left = x - bar_area_left // 小方块相对于父元素的left值
|
||||
if (move_block_left >= this.barArea.offsetWidth - parseInt(parseInt(this.blockSize.width) / 2) - 2) {
|
||||
move_block_left = this.barArea.offsetWidth - parseInt(parseInt(this.blockSize.width) / 2) - 2
|
||||
}
|
||||
if (move_block_left <= 0) {
|
||||
move_block_left = parseInt(parseInt(this.blockSize.width) / 2)
|
||||
}
|
||||
// 拖动后小方块的left值
|
||||
this.moveBlockLeft = (move_block_left - this.startLeft) + 'px'
|
||||
this.leftBarWidth = (move_block_left - this.startLeft) + 'px'
|
||||
}
|
||||
},
|
||||
|
||||
// 鼠标松开
|
||||
end: function() {
|
||||
this.endMovetime = +new Date()
|
||||
var _this = this
|
||||
// 判断是否重合
|
||||
if (this.status && this.isEnd == false) {
|
||||
var moveLeftDistance = parseInt((this.moveBlockLeft || '').replace('px', ''))
|
||||
moveLeftDistance = moveLeftDistance * 310 / parseInt(this.setSize.imgWidth)
|
||||
const data = {
|
||||
captchaType: this.captchaType,
|
||||
'pointJson': this.secretKey ? aesEncrypt(JSON.stringify({ x: moveLeftDistance, y: 5.0 }), this.secretKey) : JSON.stringify({ x: moveLeftDistance, y: 5.0 }),
|
||||
'token': this.backToken
|
||||
}
|
||||
reqCheck(data).then(res => {
|
||||
if (res.repCode == '0000') {
|
||||
this.moveBlockBackgroundColor = '#5cb85c'
|
||||
this.leftBarBorderColor = '#5cb85c'
|
||||
this.iconColor = '#fff'
|
||||
this.iconClass = 'icon-check'
|
||||
this.showRefresh = false
|
||||
this.isEnd = true
|
||||
if (this.mode == 'pop') {
|
||||
setTimeout(() => {
|
||||
this.$parent.clickShow = false
|
||||
this.refresh()
|
||||
}, 1500)
|
||||
}
|
||||
this.passFlag = true
|
||||
this.tipWords = `${((this.endMovetime - this.startMoveTime) / 1000).toFixed(2)}s验证成功`
|
||||
var captchaVerification = this.secretKey ? aesEncrypt(this.backToken + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 }), this.secretKey) : this.backToken + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 })
|
||||
setTimeout(() => {
|
||||
this.tipWords = ''
|
||||
this.$parent.closeBox()
|
||||
this.$parent.$emit('success', { captchaVerification })
|
||||
}, 1000)
|
||||
} else {
|
||||
this.moveBlockBackgroundColor = '#d9534f'
|
||||
this.leftBarBorderColor = '#d9534f'
|
||||
this.iconColor = '#fff'
|
||||
this.iconClass = 'icon-close'
|
||||
this.passFlag = false
|
||||
setTimeout(function() {
|
||||
_this.refresh()
|
||||
}, 1000)
|
||||
this.$parent.$emit('error', this)
|
||||
this.tipWords = '验证失败'
|
||||
setTimeout(() => {
|
||||
this.tipWords = ''
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
this.status = false
|
||||
}
|
||||
},
|
||||
|
||||
refresh: function() {
|
||||
this.showRefresh = true
|
||||
this.finishText = ''
|
||||
|
||||
this.transitionLeft = 'left .3s'
|
||||
this.moveBlockLeft = 0
|
||||
|
||||
this.leftBarWidth = undefined
|
||||
this.transitionWidth = 'width .3s'
|
||||
|
||||
this.leftBarBorderColor = '#ddd'
|
||||
this.moveBlockBackgroundColor = '#fff'
|
||||
this.iconColor = '#000'
|
||||
this.iconClass = 'icon-right'
|
||||
this.isEnd = false
|
||||
|
||||
this.getPictrue()
|
||||
setTimeout(() => {
|
||||
this.transitionWidth = ''
|
||||
this.transitionLeft = ''
|
||||
this.text = this.explain
|
||||
}, 300)
|
||||
},
|
||||
|
||||
// 请求背景图片和验证图片
|
||||
getPictrue() {
|
||||
const data = {
|
||||
captchaType: this.captchaType,
|
||||
clientUid: localStorage.getItem('slider'),
|
||||
ts: Date.now(), // 现在的时间戳
|
||||
}
|
||||
reqGet(data).then(res => {
|
||||
if (res.repCode == '0000') {
|
||||
this.backImgBase = res.repData.originalImageBase64
|
||||
this.blockBackImgBase = res.repData.jigsawImageBase64
|
||||
this.backToken = res.repData.token
|
||||
this.secretKey = res.repData.secretKey
|
||||
} else {
|
||||
this.tipWords = res.repMsg
|
||||
}
|
||||
|
||||
// 判断接口请求次数是否失效
|
||||
if (res.repCode == '6201') {
|
||||
this.backImgBase = null
|
||||
this.blockBackImgBase = null
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 此处可直接引用自己项目封装好的 axios 配合后端联调
|
||||
*/
|
||||
|
||||
import request from './../utils/axios' // 组件内部封装的axios
|
||||
// import request from "@/api/axios.js" //调用项目封装的axios
|
||||
|
||||
// 获取验证图片 以及token
|
||||
export function reqGet(data) {
|
||||
return request({
|
||||
url: '/captcha/get',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
// 滑动或者点选验证
|
||||
export function reqCheck(data) {
|
||||
return request({
|
||||
url: '/captcha/check',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import CryptoJS from 'crypto-js'
|
||||
/**
|
||||
* @word 要加密的内容
|
||||
* @keyWord String 服务器随机返回的关键字
|
||||
* */
|
||||
export function aesEncrypt(word, keyWord = 'XwKsGlMcdPMEhR1B') {
|
||||
var key = CryptoJS.enc.Utf8.parse(keyWord)
|
||||
var srcs = CryptoJS.enc.Utf8.parse(word)
|
||||
var encrypted = CryptoJS.AES.encrypt(srcs, key, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 })
|
||||
return encrypted.toString()
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import axios from 'axios'
|
||||
import projectConfig from '@/core/config';
|
||||
|
||||
axios.defaults.baseURL = projectConfig.baseUrl
|
||||
|
||||
const service = axios.create({
|
||||
timeout: 40000,
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Content-Type': 'application/json; charset=UTF-8'
|
||||
},
|
||||
})
|
||||
service.interceptors.request.use(
|
||||
config => {
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
// response interceptor
|
||||
service.interceptors.response.use(
|
||||
response => {
|
||||
const res = response.data
|
||||
return res
|
||||
},
|
||||
error => {
|
||||
}
|
||||
)
|
||||
export default service
|
||||
@@ -0,0 +1,36 @@
|
||||
export function resetSize(vm) {
|
||||
var img_width, img_height, bar_width, bar_height // 图片的宽度、高度,移动条的宽度、高度
|
||||
|
||||
var parentWidth = vm.$el.parentNode.offsetWidth || window.offsetWidth
|
||||
var parentHeight = vm.$el.parentNode.offsetHeight || window.offsetHeight
|
||||
|
||||
if (vm.imgSize.width.indexOf('%') != -1) {
|
||||
img_width = parseInt(this.imgSize.width) / 100 * parentWidth + 'px'
|
||||
} else {
|
||||
img_width = this.imgSize.width
|
||||
}
|
||||
|
||||
if (vm.imgSize.height.indexOf('%') != -1) {
|
||||
img_height = parseInt(this.imgSize.height) / 100 * parentHeight + 'px'
|
||||
} else {
|
||||
img_height = this.imgSize.height
|
||||
}
|
||||
|
||||
if (vm.barSize.width.indexOf('%') != -1) {
|
||||
bar_width = parseInt(this.barSize.width) / 100 * parentWidth + 'px'
|
||||
} else {
|
||||
bar_width = this.barSize.width
|
||||
}
|
||||
|
||||
if (vm.barSize.height.indexOf('%') != -1) {
|
||||
bar_height = parseInt(this.barSize.height) / 100 * parentHeight + 'px'
|
||||
} else {
|
||||
bar_height = this.barSize.height
|
||||
}
|
||||
|
||||
return { imgWidth: img_width, imgHeight: img_height, barWidth: bar_width, barHeight: bar_height }
|
||||
}
|
||||
|
||||
export const _code_chars = [1, 2, 3, 4, 5, 6, 7, 8, 9, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
|
||||
export const _code_color1 = ['#fffff0', '#f0ffff', '#f0fff0', '#fff0f0']
|
||||
export const _code_color2 = ['#FF0033', '#006699', '#993366', '#FF9900', '#66CC66', '#FF33CC']
|
||||
Reference in New Issue
Block a user