commit:同步2.3版本

This commit is contained in:
Jerry
2022-02-20 13:40:36 +08:00
parent c7cc3f5354
commit cbe0f7947d
668 changed files with 172592 additions and 821 deletions

View File

@@ -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>

View File

@@ -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;

View File

@@ -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>

View File

@@ -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>

View File

@@ -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"
]

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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
})
}

View File

@@ -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()
}

View File

@@ -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

View File

@@ -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']