parent
e4c2151603
commit
78ce11a336
@ -1,51 +0,0 @@ |
||||
<script type="text/jsx"> |
||||
export default { |
||||
name: 'DefaultEmptyImage', |
||||
|
||||
functional: true, |
||||
|
||||
render() { |
||||
return ( |
||||
<svg width="184" height="152" viewBox="0 0 184 152" xmlns="http://www.w3.org/2000/svg"> |
||||
<g fill="none" fillRule="evenodd"> |
||||
<g transform="translate(24 31.67)"> |
||||
<ellipse |
||||
fillOpacity=".8" |
||||
fill="#F5F5F7" |
||||
cx="67.797" |
||||
cy="106.89" |
||||
rx="67.797" |
||||
ry="12.668" |
||||
/> |
||||
<path |
||||
d="M122.034 69.674L98.109 40.229c-1.148-1.386-2.826-2.225-4.593-2.225h-51.44c-1.766 0-3.444.839-4.592 2.225L13.56 69.674v15.383h108.475V69.674z" |
||||
fill="#AEB8C2" |
||||
/> |
||||
<path |
||||
d="M101.537 86.214L80.63 61.102c-1.001-1.207-2.507-1.867-4.048-1.867H31.724c-1.54 0-3.047.66-4.048 1.867L6.769 86.214v13.792h94.768V86.214z" |
||||
fill="url(#linearGradient-1)" |
||||
transform="translate(13.56)" |
||||
/> |
||||
<path |
||||
d="M33.83 0h67.933a4 4 0 0 1 4 4v93.344a4 4 0 0 1-4 4H33.83a4 4 0 0 1-4-4V4a4 4 0 0 1 4-4z" |
||||
fill="#F5F5F7" |
||||
/> |
||||
<path |
||||
d="M42.678 9.953h50.237a2 2 0 0 1 2 2V36.91a2 2 0 0 1-2 2H42.678a2 2 0 0 1-2-2V11.953a2 2 0 0 1 2-2zM42.94 49.767h49.713a2.262 2.262 0 1 1 0 4.524H42.94a2.262 2.262 0 0 1 0-4.524zM42.94 61.53h49.713a2.262 2.262 0 1 1 0 4.525H42.94a2.262 2.262 0 0 1 0-4.525zM121.813 105.032c-.775 3.071-3.497 5.36-6.735 5.36H20.515c-3.238 0-5.96-2.29-6.734-5.36a7.309 7.309 0 0 1-.222-1.79V69.675h26.318c2.907 0 5.25 2.448 5.25 5.42v.04c0 2.971 2.37 5.37 5.277 5.37h34.785c2.907 0 5.277-2.421 5.277-5.393V75.1c0-2.972 2.343-5.426 5.25-5.426h26.318v33.569c0 .617-.077 1.216-.221 1.789z" |
||||
fill="#DCE0E6" |
||||
/> |
||||
</g> |
||||
<path |
||||
d="M149.121 33.292l-6.83 2.65a1 1 0 0 1-1.317-1.23l1.937-6.207c-2.589-2.944-4.109-6.534-4.109-10.408C138.802 8.102 148.92 0 161.402 0 173.881 0 184 8.102 184 18.097c0 9.995-10.118 18.097-22.599 18.097-4.528 0-8.744-1.066-12.28-2.902z" |
||||
fill="#DCE0E6" |
||||
/> |
||||
<g transform="translate(149.65 15.383)" fill="#FFF"> |
||||
<ellipse cx="20.654" cy="3.167" rx="2.849" ry="2.815"/> |
||||
<path d="M5.698 5.63H0L2.898.704zM9.259.704h4.985V5.63H9.259z"/> |
||||
</g> |
||||
</g> |
||||
</svg> |
||||
) |
||||
} |
||||
} |
||||
</script> |
||||
@ -1,40 +0,0 @@ |
||||
<script type="text/jsx"> |
||||
/** |
||||
* 从ant-design-vue处拷贝 |
||||
*/ |
||||
import DefaultEmptyImage from "./DefaultEmptyImage" |
||||
|
||||
export default { |
||||
name: "AEmpty", |
||||
|
||||
functional: true, |
||||
|
||||
props: { |
||||
description: String, |
||||
image: [String, Object], |
||||
imageStyle: [String, Object] |
||||
}, |
||||
|
||||
render(h, context) { |
||||
const {description = '暂无数据', image, imageStyle} = context.props |
||||
const prefixCls = 'ant-empty' |
||||
const cls = {[prefixCls]: true} |
||||
|
||||
let imageNode = null |
||||
if (typeof image === 'string') { |
||||
imageNode = <img alt={description} src={image}/> |
||||
} |
||||
else imageNode = <DefaultEmptyImage/> |
||||
|
||||
return ( |
||||
<div class={cls} {...{on: context.listeners}}> |
||||
<div class={`${prefixCls}-image`} style={imageStyle}>{imageNode}</div> |
||||
{description && <p class={`${prefixCls}-description`}>{description}</p>} |
||||
{context.children && <div class={`${prefixCls}-footer`}>{context.children}</div>} |
||||
</div> |
||||
) |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" src="./style.scss"></style> |
||||
@ -1,47 +0,0 @@ |
||||
.ant-empty { |
||||
padding: 20px; |
||||
margin: 0 8px; |
||||
font-size: 14px; |
||||
line-height: 22px; |
||||
text-align: center; |
||||
|
||||
&-image { |
||||
height: 100px; |
||||
margin-bottom: 8px; |
||||
|
||||
img { |
||||
height: 100%; |
||||
} |
||||
|
||||
svg { |
||||
height: 100%; |
||||
margin: auto; |
||||
} |
||||
} |
||||
|
||||
&-description { |
||||
margin: 0; |
||||
} |
||||
|
||||
&-footer { |
||||
margin-top: 16px; |
||||
} |
||||
|
||||
&-normal { |
||||
margin: 32px 0; |
||||
color: rgba(0, 0, 0, .25); |
||||
|
||||
.ant-empty-image { |
||||
height: 40px; |
||||
} |
||||
} |
||||
|
||||
&-small { |
||||
margin: 8px 0; |
||||
color: rgba(0, 0, 0, .25); |
||||
|
||||
.ant-empty-image { |
||||
height: 35px; |
||||
} |
||||
} |
||||
} |
||||
@ -1,24 +0,0 @@ |
||||
<script type="text/jsx"> |
||||
export default { |
||||
name: "LoadingMask", |
||||
|
||||
functional: true, |
||||
|
||||
props: {show: Boolean}, |
||||
|
||||
render(h, context) { |
||||
if (context.props.show) { |
||||
return ( |
||||
<div class="el-loading-mask"> |
||||
<div class="el-loading-spinner"> |
||||
<svg viewBox="25 25 50 50" class="circular"> |
||||
<circle cx="50" cy="50" r="20" fill="none" class="path"/> |
||||
</svg> |
||||
<p>加载中...</p> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
@ -1,48 +0,0 @@ |
||||
<script type="text/jsx"> |
||||
import cssVariables from '@/asset/style/variables.scss' |
||||
import LoadingMask from './LoadingMask' |
||||
|
||||
export default { |
||||
name: "AbstractDialog", |
||||
|
||||
functional: true, |
||||
|
||||
props: { |
||||
value: Boolean, |
||||
loading: {type: Boolean, default: true}, |
||||
width: {type: String, default: '30%'} |
||||
}, |
||||
|
||||
render(h, context) { |
||||
const {data, children, listeners, props: {value, loading, width}} = context |
||||
|
||||
const onClose = function () { |
||||
return listeners.close ? () => listeners.close(false) : () => ({}) |
||||
}() |
||||
|
||||
return ( |
||||
<el-dialog |
||||
v-drag-dialog |
||||
visible={value} |
||||
custom-class="abstract-dialog" |
||||
top={cssVariables.dialogTop} |
||||
width={width} |
||||
on-close={onClose} |
||||
{...data} |
||||
> |
||||
<div class="abstract-dialog-container"> |
||||
<LoadingMask show={loading}/> |
||||
<el-scrollbar> |
||||
<div class="abstract-dialog-body"> |
||||
{children} |
||||
</div> |
||||
<div style="margin-bottom: 17px"/> |
||||
</el-scrollbar> |
||||
</div> |
||||
</el-dialog> |
||||
) |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" src="./style.scss"></style> |
||||
@ -1,18 +0,0 @@ |
||||
.abstract-dialog { |
||||
.el-dialog__body { |
||||
padding: 0; |
||||
} |
||||
|
||||
.abstract-dialog-container { |
||||
position: relative; |
||||
width: 100%; |
||||
|
||||
.abstract-dialog-body { |
||||
padding: 24px 24px 0 |
||||
} |
||||
|
||||
.el-form { |
||||
padding: 0; |
||||
} |
||||
} |
||||
} |
||||
@ -1,24 +0,0 @@ |
||||
<script type="text/jsx"> |
||||
export default { |
||||
name: "AbstractForm", |
||||
|
||||
functional: true, |
||||
|
||||
render(h, context) { |
||||
const {data, children} = context |
||||
return ( |
||||
<el-form |
||||
ref="form" |
||||
label-position="right" |
||||
label-width="100px" |
||||
label-suffix=":" |
||||
size="small" |
||||
status-icon |
||||
{...data} |
||||
> |
||||
<el-row>{children}</el-row> |
||||
</el-form> |
||||
) |
||||
} |
||||
} |
||||
</script> |
||||
@ -1,25 +0,0 @@ |
||||
<script type="text/jsx"> |
||||
export default { |
||||
name: "AbstractFormItem", |
||||
|
||||
functional: true, |
||||
|
||||
props: {full: Boolean, thin: Boolean, label: String, prop: String}, |
||||
|
||||
render(h, context) { |
||||
const {full, thin, label, prop} = context.props |
||||
let props = {xs: 24, sm: 12, lg: 8} |
||||
|
||||
if (full) props = {span: 24} |
||||
else if (thin) props.lg = 12 |
||||
|
||||
return ( |
||||
<el-col {...{props}}> |
||||
<el-form-item label={label} prop={prop}> |
||||
{context.children} |
||||
</el-form-item> |
||||
</el-col> |
||||
) |
||||
} |
||||
} |
||||
</script> |
||||
@ -1,28 +0,0 @@ |
||||
<script type="jsx"> |
||||
export default { |
||||
name: "AbstractPagination", |
||||
|
||||
functional: true, |
||||
|
||||
props: { |
||||
model: { |
||||
type: Object, |
||||
default: () => ({total: 0, page: 1, pageSize: 15}) |
||||
} |
||||
}, |
||||
|
||||
render(h, context) { |
||||
const {model: {total, page, pageSize}} = context.props |
||||
return ( |
||||
<el-pagination |
||||
background |
||||
current-page={page} |
||||
page-size={pageSize} |
||||
total={total} |
||||
layout="total, prev, pager, next, jumper" |
||||
{...context.data} |
||||
/> |
||||
) |
||||
} |
||||
} |
||||
</script> |
||||
@ -1,32 +0,0 @@ |
||||
<script type="text/jsx"> |
||||
import Empty from "@/component/Empty" |
||||
|
||||
export default { |
||||
name: "AbstractTable", |
||||
|
||||
functional: true, |
||||
|
||||
render(h, context) { |
||||
const {data, children} = context |
||||
|
||||
if (!data.scopedSlots) { |
||||
data.scopedSlots = {} |
||||
} |
||||
if (!data.scopedSlots.empty) { |
||||
data.scopedSlots.empty = () => <Empty/> |
||||
} |
||||
|
||||
return ( |
||||
<el-table |
||||
ref="table" |
||||
current-row-key="id" |
||||
row-key="id" |
||||
highlight-current-row |
||||
{...data} |
||||
> |
||||
{children} |
||||
</el-table> |
||||
) |
||||
} |
||||
} |
||||
</script> |
||||
@ -1,39 +0,0 @@ |
||||
import Vue from 'vue' |
||||
import Main from './main.vue' |
||||
import {mergeObj} from "@/util" |
||||
|
||||
const GuideConstructor = Vue.extend(Main) |
||||
|
||||
let instance |
||||
|
||||
const defaultOptions = { |
||||
stageBackground: '#ffffff', |
||||
doneBtnText: '完成', |
||||
closeBtnText: '关闭', |
||||
nextBtnText: '下一步', |
||||
prevBtnText: '上一步', |
||||
} |
||||
|
||||
const guide = function (steps, index, beforeExit) { |
||||
if (instance) instance.exit() |
||||
else { |
||||
instance = new GuideConstructor({data: {options: defaultOptions}}) |
||||
instance.$mount() |
||||
document.body.appendChild(instance.$el) |
||||
} |
||||
|
||||
instance.steps = steps |
||||
instance.beforeExit = beforeExit |
||||
|
||||
this.$nextTick(() => instance.start(index)) |
||||
} |
||||
|
||||
guide.config = function (config) { |
||||
mergeObj(defaultOptions, config) |
||||
} |
||||
|
||||
guide.exit = function (force) { |
||||
instance && instance.exit(force) |
||||
} |
||||
|
||||
export default guide |
||||
@ -1,237 +0,0 @@ |
||||
<template> |
||||
<div> |
||||
<el-popover ref="popover" :value="showPopover" trigger="manual"> |
||||
<div class="guide-popover-container"> |
||||
<div v-html="step.content"/> |
||||
|
||||
<div class="guide-popover-action"> |
||||
<div class="action-close"> |
||||
<el-button size="mini" type="dashed" plain @click="exit"> |
||||
{{ hasDone ? step.doneBtnText : step.closeBtnText }} |
||||
</el-button> |
||||
</div> |
||||
|
||||
<div class="action-step"> |
||||
<el-button v-if="showPrevBtn" class="prev-btn" size="mini" plain @click="previous"> |
||||
{{ step.prevBtnText }} |
||||
</el-button> |
||||
<el-button v-if="showNextBtn" class="next-btn" size="mini" plain @click="next"> |
||||
{{ step.nextBtnText }} |
||||
</el-button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<div slot="reference" v-show="showStage" id="guide-stage" :style="stageStyle"/> |
||||
</el-popover> |
||||
|
||||
<div v-if="showStage" id="guide-highlight-element-cover" :style="stageStyle" @click.prevent.stop/> |
||||
|
||||
<div v-if="showOverlay" id="guide-overlay"/> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
/** |
||||
* 由driver.js改造而来 |
||||
*/ |
||||
import {debounce, isEmpty, deepClone} from "@/util" |
||||
import {addHighlightClasses, getCalculatedPosition, removeHighlightClasses} from "./utils" |
||||
|
||||
export default { |
||||
name: "Guide", |
||||
|
||||
data() { |
||||
return { |
||||
steps: [], |
||||
options: {}, |
||||
beforeExit: null, |
||||
isActive: false, |
||||
showStage: false, |
||||
showOverlay: false, |
||||
currentStep: 0, |
||||
moving: false, |
||||
stageStyle: '', |
||||
highlightedElement: null, |
||||
lastHighlightedElement: null |
||||
} |
||||
}, |
||||
|
||||
computed: { |
||||
showPopover() { |
||||
return !this.moving && this.showStage |
||||
}, |
||||
showPrevBtn() { |
||||
return this.step.forceShowPrevBtn || this.currentStep > 0 && this.steps[this.currentStep - 1] |
||||
}, |
||||
showNextBtn() { |
||||
return this.step.forceShowNextBtn || this.steps[this.currentStep + 1] |
||||
}, |
||||
hasDone() { |
||||
return this.currentStep === this.steps.length - 1 |
||||
}, |
||||
step() { |
||||
if (!this.isActive || this.steps.length <= 0 || !this.steps[this.currentStep]) { |
||||
return {} |
||||
} |
||||
|
||||
return { |
||||
...this.options, |
||||
...this.steps[this.currentStep], |
||||
index: this.currentStep |
||||
} |
||||
} |
||||
}, |
||||
|
||||
watch: { |
||||
isActive(v) { |
||||
document.body.classList[v ? 'add' : 'remove']('overflow-hidden') |
||||
} |
||||
}, |
||||
|
||||
methods: { |
||||
start(index = 0) { |
||||
if (!this.steps || this.steps.length === 0) throw new Error('请传入步骤') |
||||
|
||||
if (index === 'last') index = this.steps.length - 1 |
||||
else if (index === 'first') index = 0 |
||||
|
||||
if (index > this.steps.length - 1 || !this.steps[index]) { |
||||
console.error('error index:', index, 'error steps:', deepClone(this.steps)) |
||||
throw new Error('起始步骤有误') |
||||
} |
||||
|
||||
this.isActive = true |
||||
this.currentStep = index |
||||
this.showOverlay = false |
||||
this.showStage = false |
||||
|
||||
this.$_highlight() |
||||
|
||||
this.showOverlay = true |
||||
this.showStage = true |
||||
}, |
||||
exit(force) { |
||||
if (!this.isActive) return |
||||
|
||||
if (!force && this.beforeExit) { |
||||
const result = this.beforeExit(this.hasDone) |
||||
if (result && result.then) { |
||||
result.then(this.$_clear) |
||||
} |
||||
else result !== false && this.$_clear() |
||||
return |
||||
} |
||||
|
||||
this.$_clear() |
||||
}, |
||||
|
||||
previous() { |
||||
if (!this.isActive || this.moving) return |
||||
this.moving = true |
||||
|
||||
if (this.step.onPrevious) { |
||||
const result = this.step.onPrevious() |
||||
if (result && result.then) { |
||||
result.then(this.$_movePrevious, this.$_moveEnd) |
||||
} |
||||
else result ? this.$_movePrevious() : this.$_moveEnd() |
||||
} |
||||
else this.$_movePrevious() |
||||
}, |
||||
next() { |
||||
if (!this.isActive || this.moving) return |
||||
this.moving = true |
||||
|
||||
if (this.step.onNext) { |
||||
const result = this.step.onNext() |
||||
if (result && result.then) { |
||||
result.then(this.$_moveNext, this.$_moveEnd) |
||||
} |
||||
else result ? this.$_moveNext() : this.$_moveEnd() |
||||
} |
||||
else this.$_moveNext() |
||||
}, |
||||
|
||||
resize() { |
||||
if (!this.highlightedElement) return |
||||
this.$_setStageStyle(getCalculatedPosition(this.highlightedElement)) |
||||
this.$nextTick(this.$refs.popover.updatePopper) |
||||
}, |
||||
|
||||
$_movePrevious() { |
||||
if (!this.isActive) return |
||||
this.currentStep-- |
||||
this.$_highlight() |
||||
}, |
||||
$_moveNext() { |
||||
if (!this.isActive) return |
||||
this.currentStep++ |
||||
this.$_highlight() |
||||
}, |
||||
$_moveEnd() { |
||||
this.$refs.popover.updatePopper() |
||||
this.moving = false |
||||
}, |
||||
$_clear() { |
||||
if (!this.isActive) return |
||||
this.isActive = false |
||||
this.showOverlay = false |
||||
this.showStage = false |
||||
this.currentStep = 0 |
||||
this.steps = [] |
||||
this.beforeExit = null |
||||
this.highlightedElement && removeHighlightClasses(this.highlightedElement) |
||||
this.highlightedElement = null |
||||
this.lastHighlightedElement = null |
||||
}, |
||||
$_highlight() { |
||||
if (isEmpty(this.step.element)) throw new Error(`step${this.currentStep}中element为空`) |
||||
|
||||
const el = document.querySelector(this.step.element) |
||||
|
||||
if (!el) throw new Error(`${this.step.element} 没有该元素`) |
||||
|
||||
if (el === this.highlightedElement) return |
||||
|
||||
this.highlightedElement && removeHighlightClasses(this.highlightedElement) |
||||
|
||||
this.lastHighlightedElement = this.highlightedElement |
||||
this.highlightedElement = el |
||||
|
||||
el.scrollIntoView({block: 'center'}) |
||||
|
||||
this.$_setStageStyle(getCalculatedPosition(el)) |
||||
|
||||
window.setTimeout(() => { |
||||
this.$_moveEnd() |
||||
this.step.onHighlighted && this.step.onHighlighted() |
||||
}, 300) |
||||
|
||||
addHighlightClasses(el) |
||||
}, |
||||
$_setStageStyle({top, left, right, bottom}) { |
||||
const width = right - left |
||||
const height = bottom - top |
||||
this.stageStyle = |
||||
`width:${width}px;` + |
||||
`height:${height}px;` + |
||||
`top:${top}px;` + |
||||
`left:${left}px;` + |
||||
`background-color:${this.step.stageBackground}` |
||||
} |
||||
}, |
||||
|
||||
mounted() { |
||||
this.resize = debounce(this.resize) |
||||
|
||||
window.addEventListener('resize', this.resize) |
||||
|
||||
this.$once('hook:beforeDestroy', () => { |
||||
window.removeEventListener('resize', this.resize) |
||||
}) |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" src="./style.scss"></style> |
||||
@ -1,70 +0,0 @@ |
||||
.guide-highlighted-element { |
||||
z-index: 1002 !important; |
||||
} |
||||
|
||||
.guide-position-relative { |
||||
position: relative !important; |
||||
} |
||||
|
||||
.guide-fix-stacking { |
||||
z-index: auto !important; |
||||
opacity: 1.0 !important; |
||||
transform: none !important; |
||||
filter: none !important; |
||||
perspective: none !important; |
||||
transform-style: flat !important; |
||||
will-change: unset !important; |
||||
} |
||||
|
||||
.guide-popover-container { |
||||
min-width: 300px; |
||||
|
||||
.guide-popover-action { |
||||
position: relative; |
||||
height: 100%; |
||||
width: 100%; |
||||
margin-top: 20px; |
||||
|
||||
.action-close { |
||||
display: inline-block; |
||||
} |
||||
|
||||
.action-step { |
||||
display: inline-block; |
||||
right: 0; |
||||
position: absolute; |
||||
|
||||
.prev-btn { |
||||
position: absolute; |
||||
right: 90px; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
#guide-stage { |
||||
position: absolute; |
||||
background: white; |
||||
transition: all 0.3s; |
||||
z-index: 1001 !important; |
||||
border-radius: 2px; |
||||
/*background: transparent !important; |
||||
outline: 5000px solid rgba(0, 0, 0, 0.75);*/ |
||||
} |
||||
|
||||
#guide-overlay { |
||||
background: black; |
||||
position: fixed; |
||||
top: 0; |
||||
left: 0; |
||||
bottom: 0; |
||||
right: 0; |
||||
opacity: 0.75; |
||||
z-index: 1000 !important; |
||||
} |
||||
|
||||
#guide-highlight-element-cover { |
||||
position: absolute; |
||||
background: transparent !important; |
||||
z-index: 1003 !important; |
||||
} |
||||
@ -1,106 +0,0 @@ |
||||
const CLASS_HIGHLIGHTED_ELEMENT = 'guide-highlighted-element' |
||||
const CLASS_POSITION_RELATIVE = 'guide-position-relative' |
||||
const CLASS_FIX_STACKING_CONTEXT = 'guide-fix-stacking' |
||||
|
||||
export function getStyleProperty(element, propertyName, prefixVendor = false) { |
||||
if (prefixVendor) { |
||||
const prefixes = ['', '-webkit-', '-ms-', 'moz-', '-o-'] |
||||
for (let counter = 0; counter < prefixes.length; counter++) { |
||||
const prefixedProperty = prefixes[counter] + propertyName |
||||
const foundValue = getStyleProperty(element, prefixedProperty) |
||||
|
||||
if (foundValue) return foundValue |
||||
} |
||||
|
||||
return '' |
||||
} |
||||
|
||||
let propertyValue = '' |
||||
|
||||
if (element.currentStyle) { |
||||
propertyValue = element.currentStyle[propertyName] |
||||
} |
||||
else if (document.defaultView && document.defaultView.getComputedStyle) { |
||||
propertyValue = |
||||
document |
||||
.defaultView |
||||
.getComputedStyle(element, null) |
||||
.getPropertyValue(propertyName) |
||||
} |
||||
|
||||
return propertyValue && propertyValue.toLowerCase ? propertyValue.toLowerCase() : propertyValue |
||||
} |
||||
|
||||
export function removeHighlightClasses(element) { |
||||
element.classList.remove(CLASS_HIGHLIGHTED_ELEMENT) |
||||
element.classList.remove(CLASS_POSITION_RELATIVE) |
||||
|
||||
const stackFixes = document.querySelectorAll(`.${CLASS_FIX_STACKING_CONTEXT}`) |
||||
for (let counter = 0; counter < stackFixes.length; counter++) { |
||||
stackFixes[counter].classList.remove(CLASS_FIX_STACKING_CONTEXT) |
||||
} |
||||
} |
||||
|
||||
export function addHighlightClasses(element) { |
||||
element.classList.add(CLASS_HIGHLIGHTED_ELEMENT) |
||||
|
||||
if (canMakeRelative(element)) { |
||||
element.classList.add(CLASS_POSITION_RELATIVE) |
||||
} |
||||
|
||||
fixStackingContext(element) |
||||
} |
||||
|
||||
export function getCalculatedPosition(element) { |
||||
const body = document.body |
||||
const documentElement = document.documentElement |
||||
|
||||
const scrollTop = window.pageYOffset || documentElement.scrollTop || body.scrollTop |
||||
const scrollLeft = window.pageXOffset || documentElement.scrollLeft || body.scrollLeft |
||||
const elementRect = element.getBoundingClientRect() |
||||
|
||||
return ({ |
||||
top: elementRect.top + scrollTop, |
||||
left: elementRect.left + scrollLeft, |
||||
right: elementRect.left + scrollLeft + elementRect.width, |
||||
bottom: elementRect.top + scrollTop + elementRect.height, |
||||
}) |
||||
} |
||||
|
||||
function canMakeRelative(element) { |
||||
const currentPosition = getStyleProperty(element, 'position') |
||||
const avoidPositionsList = ['absolute', 'fixed', 'relative'] |
||||
|
||||
return avoidPositionsList.indexOf(currentPosition) === -1 |
||||
} |
||||
|
||||
function fixStackingContext(element) { |
||||
let parentNode = element.parentNode |
||||
while (parentNode) { |
||||
if (!parentNode.tagName || parentNode.tagName.toLowerCase() === 'body') { |
||||
break |
||||
} |
||||
|
||||
const zIndex = getStyleProperty(parentNode, 'z-index') |
||||
const opacity = parseFloat(getStyleProperty(parentNode, 'opacity')) |
||||
const transform = getStyleProperty(parentNode, 'transform', true) |
||||
const transformStyle = getStyleProperty(parentNode, 'transform-style', true) |
||||
const transformBox = getStyleProperty(parentNode, 'transform-box', true) |
||||
const filter = getStyleProperty(parentNode, 'filter', true) |
||||
const perspective = getStyleProperty(parentNode, 'perspective', true) |
||||
|
||||
if ( |
||||
/[0-9]+/.test(zIndex) |
||||
|| opacity < 1 |
||||
|| (transform && transform !== 'none') |
||||
|| (transformStyle && transformStyle !== 'flat') |
||||
|| (transformBox && transformBox !== 'border-box') |
||||
|| (filter && filter !== 'none') |
||||
|| (perspective && perspective !== 'none') |
||||
) { |
||||
parentNode.classList.add(CLASS_FIX_STACKING_CONTEXT) |
||||
} |
||||
|
||||
parentNode = parentNode.parentNode |
||||
} |
||||
} |
||||
@ -1,32 +0,0 @@ |
||||
import Vue from 'vue' |
||||
import Main from './main.vue' |
||||
|
||||
const Constructor = Vue.extend(Main) |
||||
|
||||
let instance |
||||
|
||||
const signature = function ({image, lineWidth = 6, lineColor = '#000000', onConfirm}) { |
||||
if (instance) instance.close() |
||||
else { |
||||
instance = new Constructor().$mount() |
||||
document.body.appendChild(instance.$el) |
||||
} |
||||
|
||||
instance.image = image |
||||
instance.lineWidth = lineWidth |
||||
instance.lineColor = lineColor |
||||
instance.onConfirm = onConfirm |
||||
instance.init() |
||||
instance.visible = true |
||||
|
||||
return instance |
||||
} |
||||
|
||||
signature.close = function () { |
||||
if (instance) { |
||||
instance.close() |
||||
instance = null |
||||
} |
||||
} |
||||
|
||||
export default signature |
||||
@ -1,161 +0,0 @@ |
||||
<template> |
||||
<div v-show="visible" class="signature-board"> |
||||
<canvas |
||||
ref="canvas" |
||||
@mousedown.prevent="drawStart" |
||||
@mousemove.prevent="drawMove" |
||||
@mouseup.prevent="drawEnd" |
||||
@touchstart.prevent="drawStart" |
||||
@touchmove.prevent="drawMove" |
||||
@touchend.prevent="drawEnd" |
||||
/> |
||||
|
||||
<div class="signature-board__footer"> |
||||
<el-button plain size="small" @click="clear">清 空</el-button> |
||||
<el-button plain size="small" @click="close">关 闭</el-button> |
||||
<el-button size="small" type="primary" @click="confirm">确 定</el-button> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
/* |
||||
* 突然发现没办法做到竖屏横屏都支持,坑爹 |
||||
* */ |
||||
|
||||
function getEventPoint(e) { |
||||
let x, y |
||||
if (['mousedown', 'mousemove'].includes(e.type)) { |
||||
x = e.clientX - e.target.offsetLeft |
||||
y = e.clientY - e.target.offsetTop |
||||
} |
||||
else { |
||||
x = e.changedTouches[0].clientX - e.target.offsetLeft |
||||
y = e.changedTouches[0].clientY - e.target.offsetTop |
||||
} |
||||
return {x, y} |
||||
} |
||||
|
||||
export default { |
||||
name: 'SignatureBoard', |
||||
|
||||
props: { |
||||
image: String, |
||||
lineWidth: Number, |
||||
lineColor: String, |
||||
onConfirm: Function |
||||
}, |
||||
|
||||
data() { |
||||
return { |
||||
visible: false, |
||||
drew: false, |
||||
drawing: false, |
||||
startX: 0, |
||||
startY: 0 |
||||
} |
||||
}, |
||||
|
||||
methods: { |
||||
drawStart(e) { |
||||
const {x, y} = getEventPoint(e) |
||||
this.drew = true |
||||
this.drawing = true |
||||
this.startX = x |
||||
this.startY = y |
||||
this.ctx.strokeStyle = this.lineColor |
||||
this.ctx.lineWidth = this.lineWidth |
||||
}, |
||||
|
||||
drawMove(e) { |
||||
if (!this.drawing) return |
||||
const {x, y} = getEventPoint(e) |
||||
this.ctx.beginPath() |
||||
this.ctx.moveTo(this.startX, this.startY) |
||||
this.ctx.lineTo(x, y) |
||||
this.ctx.stroke() |
||||
this.ctx.closePath() |
||||
this.startY = y |
||||
this.startX = x |
||||
}, |
||||
|
||||
drawEnd() { |
||||
this.drawing = false |
||||
}, |
||||
|
||||
getBase64() { |
||||
if (!this.drew) return null |
||||
return this.$refs.canvas.toDataURL() |
||||
}, |
||||
|
||||
resize() { |
||||
const canvas = this.$refs.canvas |
||||
|
||||
canvas.height = window.innerHeight |
||||
canvas.width = window.innerWidth |
||||
}, |
||||
|
||||
init() { |
||||
this.resize() |
||||
|
||||
this.ctx = this.$refs.canvas.getContext('2d') |
||||
this.ctx.lineCap = 'round' |
||||
this.ctx.lineJoin = 'round' |
||||
|
||||
if (this.image) { |
||||
let img = new Image() |
||||
img.src = this.image |
||||
img.onload = () => { |
||||
this.ctx.drawImage(img, 0, 0, img.width, img.height) |
||||
this.drew = true |
||||
img = null |
||||
} |
||||
} |
||||
}, |
||||
|
||||
clear() { |
||||
this.drew = false |
||||
this.drawing = false |
||||
this.$refs.canvas && (this.$refs.canvas.height = window.innerHeight) |
||||
}, |
||||
|
||||
close() { |
||||
this.visible = false |
||||
this.clear() |
||||
}, |
||||
|
||||
confirm() { |
||||
this.onConfirm && this.onConfirm(this.getBase64()) |
||||
this.visible = false |
||||
} |
||||
}, |
||||
|
||||
mounted() { |
||||
window.addEventListener('resize', this.resize) |
||||
|
||||
this.$once('hook:beforeDestroy', () => { |
||||
this.clear() |
||||
window.removeEventListener('resize', this.resize) |
||||
}) |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss"> |
||||
.signature-board { |
||||
position: fixed; |
||||
background-color: #ffffff; |
||||
top: 0; |
||||
right: 0; |
||||
height: 100vh; |
||||
width: 100vw; |
||||
z-index: 100; |
||||
overflow: hidden; |
||||
|
||||
&__footer { |
||||
position: absolute; |
||||
bottom: 20px; |
||||
right: 20px; |
||||
} |
||||
} |
||||
</style> |
||||
@ -1,11 +1,7 @@ |
||||
import Guide from './Guide' |
||||
import ImageViewer from './ImageViewer' |
||||
import Verify from './Verify' |
||||
import Signature from './SignautreBoard' |
||||
|
||||
export default function (Vue) { |
||||
Vue.prototype.$guide = Guide |
||||
Vue.prototype.$image = ImageViewer |
||||
Vue.prototype.$verify = Verify |
||||
Vue.prototype.$signature = Signature |
||||
} |
||||
|
||||
@ -1,12 +0,0 @@ |
||||
export function createWorker(func, data, callback) { |
||||
const blob = new Blob(['(' + func.toString() + ')()']) |
||||
const url = window.URL.createObjectURL(blob) |
||||
const worker = new Worker(url) |
||||
|
||||
worker.onmessage = callback |
||||
worker.postMessage(data) |
||||
|
||||
window.URL.revokeObjectURL(url) |
||||
|
||||
return worker |
||||
} |
||||
@ -1,64 +0,0 @@ |
||||
<template> |
||||
<div> |
||||
<div class="tip-row"> |
||||
基于driver.js改造的用户导航 |
||||
</div> |
||||
|
||||
<div> |
||||
<el-button plain @click="onClick">开始导航</el-button> |
||||
</div> |
||||
|
||||
<div id="example-guide-step1" class="example-guide-step">我是步骤1</div> |
||||
<div id="example-guide-step2" class="example-guide-step">我是步骤2</div> |
||||
<div id="example-guide-step3" class="example-guide-step">我是步骤3</div> |
||||
<div id="example-guide-step4" class="example-guide-step">我是步骤4</div> |
||||
<div id="example-guide-step5" class="example-guide-step">我是步骤5</div> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
import {elSuccess, elConfirm} from "@/util/message" |
||||
|
||||
export default { |
||||
name: "guidePage", |
||||
|
||||
methods: { |
||||
onClick() { |
||||
this.$guide([ |
||||
{ |
||||
element: '#example-guide-step1', |
||||
content: 'onNext钩子示例', |
||||
onNext: () => elConfirm('确认进行下一步吗?', false) |
||||
}, |
||||
{ |
||||
element: '#example-guide-step2', |
||||
content: 'onPrevious钩子展示', |
||||
onPrevious: () => elConfirm('确认回到上一步吗?', false) |
||||
}, |
||||
{ |
||||
element: '#example-guide-step3', |
||||
content: 'onHighlighted钩子示例', |
||||
onHighlighted: () => elSuccess('到达第三步!') |
||||
}, |
||||
{ |
||||
element: '#example-guide-step4', |
||||
content: `<h1>内容可以是html字符串</h1><img src="https://dss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=4018557288,1217151095&fm=26&gp=0.jpg">`, |
||||
}, |
||||
{ |
||||
element: '#example-guide-step5', |
||||
content: '最后一步', |
||||
}, |
||||
]) |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style scoped> |
||||
.example-guide-step { |
||||
margin-top: 50px; |
||||
height: 200px; |
||||
width: 300px; |
||||
background-color: blueviolet; |
||||
} |
||||
</style> |
||||
@ -1,31 +0,0 @@ |
||||
<template> |
||||
<div> |
||||
<div class="tip-row"> |
||||
搞了个手写签名,竖屏横屏不可兼得 |
||||
</div> |
||||
<div> |
||||
<img :src="src" style="width: 100%;height: 500px;object-fit: contain"> |
||||
</div> |
||||
<el-button plain @click="signature">点击签名</el-button> |
||||
</div> |
||||
</template> |
||||
|
||||
<script> |
||||
export default { |
||||
name: 'signaturePage', |
||||
|
||||
data() { |
||||
return { |
||||
src: null |
||||
} |
||||
}, |
||||
|
||||
methods: { |
||||
signature() { |
||||
this.$signature({ |
||||
image: this.src, onConfirm: img => this.src = img |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
</script> |
||||
Loading…
Reference in new issue