增加行政区划选择组件

移除了改写的popover
master
toesbieya 6 years ago
parent 5794f76fe0
commit 8954fe89c0
  1. 217
      vue/element-ui-personal/components/Popover/index.vue
  2. 3
      vue/element-ui-personal/index.js
  3. 2
      vue/src/assets/styles/element-ui.scss
  4. 2
      vue/src/bizComponents/DocumentHistory/index.vue
  5. 263
      vue/src/components/RegionSelector/Tab/index.vue
  6. 178
      vue/src/components/RegionSelector/Tab/style.scss
  7. 11
      vue/src/components/RegionSelector/Tree/TreeDialog.vue
  8. 16
      vue/src/components/RegionSelector/Tree/index.vue
  9. 18
      vue/src/components/RegionSelector/index.vue
  10. 10
      vue/src/components/RegionSelector/mixin.js
  11. 6
      vue/src/router/constant/modules/example.js
  12. 2
      vue/src/utils/validate.js
  13. 41
      vue/src/views/example/components/regionSelectorExample.vue
  14. 2
      vue/src/views/stock/current/index.vue
  15. 3
      vue/src/views/system/customer/EditDialog.vue
  16. 4
      vue/src/views/system/customer/index.vue
  17. 1
      vue/src/views/system/supplier/EditDialog.vue
  18. 2
      vue/src/views/system/supplier/index.vue

@ -1,217 +0,0 @@
<template>
<div>
<slot name="reference"/>
<transition :name="transition" @after-enter="handleAfterEnter" @after-leave="handleAfterLeave">
<div
ref="popper"
v-show="!disabled && showPopper"
class="el-popover el-popper"
:class="[popperClass, content && 'el-popover--plain']"
:style="{ width: width + 'px' }"
:id="tooltipId"
>
<div class="el-popover__title" v-if="title">
<slot name="title">{{title}}</slot>
</div>
<slot>{{ content }}</slot>
</div>
</transition>
</div>
</template>
<script>
import Popper from '@ele/mixins/vue-popper'
import {on, off} from 'element-ui/lib/utils/dom'
import {addClass, removeClass} from 'element-ui/lib/utils/dom'
import {generateId} from 'element-ui/lib/utils/util'
export default {
name: "ElPopover",
mixins: [Popper],
props: {
trigger: {
type: String,
default: 'click',
validator: value => ['click', 'focus', 'hover', 'manual'].indexOf(value) > -1
},
openDelay: {type: Number, default: 0},
closeDelay: {type: Number, default: 200},
transition: {type: String, default: 'fade-in-liner'},
popperClass: String,
title: String,
disabled: Boolean,
content: String,
width: {},
arrowOffset: {type: Number, default: 0}
},
data() {
return {}
},
computed: {
tooltipId() {
return `el-popover-${generateId()}`
}
},
watch: {
showPopper(val) {
if (this.disabled) return
this.$emit(val ? 'show' : 'hide')
}
},
methods: {
doToggle() {
this.showPopper = !this.showPopper
},
doShow() {
this.showPopper = true
},
doClose() {
this.showPopper = false
},
handleFocus() {
addClass(this.referenceElm, 'focusing')
if (this.trigger === 'click' || this.trigger === 'focus') this.showPopper = true
},
handleClick() {
removeClass(this.referenceElm, 'focusing')
},
handleBlur() {
removeClass(this.referenceElm, 'focusing')
if (this.trigger === 'click' || this.trigger === 'focus') this.showPopper = false
},
handleMouseEnter() {
window.clearTimeout(this._timer)
if (this.openDelay) {
this._timer = setTimeout(() => this.showPopper = true, this.openDelay)
}
else this.showPopper = true
},
handleKeydown(ev) {
if (ev.keyCode === 27 && this.trigger !== 'manual') { // esc
this.doClose()
}
},
handleMouseLeave() {
window.clearTimeout(this._timer)
if (this.closeDelay) {
this._timer = setTimeout(() => this.showPopper = false, this.closeDelay)
}
else this.showPopper = false
},
handleDocumentClick(e) {
let reference = this.reference || this.$refs.reference
const popper = this.popper || this.$refs.popper
if (!reference && this.$slots.reference && this.$slots.reference[0]) {
reference = this.referenceElm = this.$slots.reference[0].elm
}
if (!this.$el ||
!reference ||
this.$el.contains(e.target) ||
reference.contains(e.target) ||
!popper ||
popper.contains(e.target)) return
this.showPopper = false
},
handleAfterEnter() {
this.$emit('after-enter')
},
handleAfterLeave() {
this.$emit('after-leave')
this.doDestroy()
},
cleanup() {
if (this.openDelay || this.closeDelay) {
window.clearTimeout(this._timer)
}
}
},
mounted() {
let reference = this.referenceElm = this.reference || this.$refs.reference
const popper = this.popper || this.$refs.popper
if (!reference && this.$slots.reference && this.$slots.reference[0]) {
reference = this.referenceElm = this.$slots.reference[0].elm
}
if (reference) {
addClass(reference, 'el-popover__reference')
if (this.trigger !== 'click') {
on(reference, 'focusin', () => {
this.handleFocus()
const instance = reference.__vue__
if (instance && typeof instance.focus === 'function') {
instance.focus()
}
})
on(popper, 'focusin', this.handleFocus)
on(reference, 'focusout', this.handleBlur)
on(popper, 'focusout', this.handleBlur)
}
on(reference, 'keydown', this.handleKeydown)
on(reference, 'click', this.handleClick)
}
if (this.trigger === 'click') {
on(reference, 'click', this.doToggle)
on(document, 'click', this.handleDocumentClick)
}
else if (this.trigger === 'hover') {
on(reference, 'mouseenter', this.handleMouseEnter)
on(popper, 'mouseenter', this.handleMouseEnter)
on(reference, 'mouseleave', this.handleMouseLeave)
on(popper, 'mouseleave', this.handleMouseLeave)
}
else if (this.trigger === 'focus') {
if (reference.querySelector('input, textarea')) {
on(reference, 'focusin', this.doShow)
on(reference, 'focusout', this.doClose)
}
else {
on(reference, 'mousedown', this.doShow)
on(reference, 'mouseup', this.doClose)
}
}
},
beforeDestroy() {
this.cleanup()
const reference = this.reference
off(reference, 'click', this.doToggle)
off(reference, 'mouseup', this.doClose)
off(reference, 'mousedown', this.doShow)
off(reference, 'focusin', this.doShow)
off(reference, 'focusout', this.doClose)
off(reference, 'mousedown', this.doShow)
off(reference, 'mouseup', this.doClose)
off(reference, 'mouseleave', this.handleMouseLeave)
off(reference, 'mouseenter', this.handleMouseEnter)
off(document, 'click', this.handleDocumentClick)
},
deactivated() {
this.cleanup()
},
}
</script>

@ -1,8 +1,7 @@
import Message from "./components/Message"
import Popover from './components/Popover'
import Select from './components/Select/Select'
const components = [Popover, Select]
const components = [Select]
export default function (Vue) {
components.forEach(component => {

@ -20,6 +20,8 @@
}
.el-card {
overflow: visible;
.el-card__body {
height: 100%;
}

@ -11,7 +11,7 @@
if (type === 'add') return ''
return (
<el-popover
onShow={context.listeners.show}
v-on:show={context.listeners.show}
placement="bottom"
width="250"
trigger="click"

@ -0,0 +1,263 @@
<template>
<el-select
ref="select"
:value="value"
:disabled="readonly"
:size="size"
clearable
@clear="() => $emit('clear')"
@visible-change="visibleChange"
>
<template slot="empty">
<div class="rg-header">
<span>行政区划选择器</span>
<button class="rg-removeall-button" type="button" title="清除已选" @click="removeAll">
<i class="el-icon-delete"/>
</button>
<button class="rg-done-button" type="button" title="完成" @click="done">
<i class="el-icon-check"/>
</button>
</div>
<div class="rg-search">
<input
ref="searchInput"
v-model="searchText"
class="rg-input"
type="text"
placeholder="输入地区id或名称,回车搜索"
@keyup.enter="search"
>
</div>
<div class="rg-level-tabs">
<ul>
<li v-for="(tab,index) in selected" :key="tab.name" :class="{active:index+1 === currentLevel}">
<a href="javascript:" @click="()=>clickTab(index,tab)">{{tab.name}}</a>
</li>
</ul>
</div>
<div v-loading="loading" class="rg-results-container">
<ul class="rg-results">
<li
v-for="item in items"
:key="item.id"
:class="{'rg-item': true, 'active': isItemActive(item.id)}"
@click="()=>clickItem(item)"
>
{{limit ? item.name + `(${item.value})` : item.name}}
</li>
<li v-if="items.length === 0" class="rg-message-box">无匹配项目</li>
</ul>
</div>
</template>
</el-select>
</template>
<script>
import {createLimitTree, getNodeId} from "@/utils/tree"
import common from '../mixin'
const PROVINCE_CITIES = ['北京市', '天津市', '上海市', '重庆市']
const DEFAULT_TABS = [{name: '省/直辖市'}, {name: '市'}, {name: '区/县'}, {name: '乡/镇/街道'}]
const DEFAULT_MAX_LEVEL = 4
export default {
name: "RegionTabSelector",
mixins: [common],
props: {
value: [String, Array],
separation: {type: String, default: ','}
},
data() {
return {
loading: false,
searchText: '',
currentLevel: 1,
maxLevel: DEFAULT_MAX_LEVEL,
selected: [],
treeData: [],
items: []
}
},
computed: {
regionTree() {
return this.$store.state.dataCache.regionTree
},
},
watch: {
currentLevel(v) {
this.setItems(v)
}
},
methods: {
//select
visibleChange(v) {
if (v) {
this.transformValueToSelected()
this.$nextTick(() => this.$refs.searchInput.focus())
}
else this.removeAll()
},
search() {
const parent = this.selected[this.currentLevel - 2] || {children: this.treeData}
if (!this.searchText) return this.items = parent.children
const predicate = item => item.id.startsWith(this.searchText) || item.name.startsWith(this.searchText)
this.items = parent.children.filter(predicate)
},
clickTab(index, tab) {
//tab
if (index + 1 === this.currentLevel) return
this.currentLevel = index + 1
},
clickItem(item) {
this.setTabs(item, this.currentLevel)
this.nextTab()
},
setTabs(item, level) {
const prev = this.selected.slice(0, level - 1)
prev.push(item)
const next = JSON.parse(JSON.stringify(DEFAULT_TABS))
if (level === 1) {
//-1''tab
if (PROVINCE_CITIES.includes(item.name)) {
next.splice(1, 1)
this.maxLevel = DEFAULT_MAX_LEVEL - 1
}
else this.maxLevel = DEFAULT_MAX_LEVEL
}
this.selected = prev.concat(next.slice(level, this.maxLevel))
},
nextTab() {
this.searchText = ''
//
if (this.currentLevel >= this.maxLevel) {
this.done()
}
else this.currentLevel++
},
//
isItemActive(id) {
const selected = this.selected[this.currentLevel - 1]
return selected && selected.id === id
},
//tab
setItems(level) {
//
if (level === 1) this.items = this.treeData
//children
else {
const parent = this.selected[level - 2]
this.items = parent ? parent.children || [] : []
}
},
//value
transformValueToSelected() {
if (!this.value) return
const result = [],
loopArray = Array.isArray(this.value)
? this.value
: this.value.split(this.separation).filter(Boolean)
for (let i = 0; i < loopArray.length; i++) {
const str = loopArray[i],
parent = result[i - 1] || {children: this.treeData}
const node = parent.children.find(predicate(str))
if (!node) break
result.push(node)
}
result.forEach((item, index) => this.setTabs(item, index + 1))
},
removeAll() {
this.maxLevel = DEFAULT_MAX_LEVEL
this.selected = JSON.parse(JSON.stringify(DEFAULT_TABS))
this.currentLevel = 1
},
done() {
const result = this.selected.filter(item => item.id)
this.$emit('input', result.map(item => item.name).join(this.separation))
if (result.length === 0) {
this.$emit('select', {}, [])
return this.$refs.select.blur()
}
const node = result[result.length - 1]
const payload = [node]
if (this.getChildrenOnSelect) {
const ids = getNodeId(node.children)
ids.unshift(node.id)
payload.push(ids)
}
this.$emit('select', ...payload)
this.$refs.select.blur()
},
init() {
this.loading = true
const hasInit = this.regionTree.length > 0
const promise = () => hasInit ? Promise.resolve() : this.$store.dispatch('dataCache/initRegion')
return promise()
.then(() => this.limit ? this.limitApi() : Promise.resolve())
.then(data => {
this.treeData = data ? createLimitTree(this.regionTree, data) : this.regionTree
this.setItems(this.currentLevel)
})
.finally(() => this.loading = false)
}
},
created() {
this.selected = JSON.parse(JSON.stringify(DEFAULT_TABS))
},
mounted() {
this.init()
}
}
//node{id:'10',...}{name:'',...}'10'''
function predicate(node) {
if (typeof node === 'object') {
const key = node.hasOwnProperty('id') ? 'id' : 'name'
return item => item[key] === node[key]
}
else {
const firstCharCode = node.charCodeAt(0)
const key = 48 <= firstCharCode && firstCharCode <= 57 ? 'id' : 'name'
return item => item[key] === node
}
}
</script>
<style lang="scss">
@import "./style.scss";
</style>

@ -0,0 +1,178 @@
.rg-header, .rg-search {
padding: 2px 10px 0;
background-color: white;
}
.rg-header {
h3 {
line-height: normal;
padding: 6px 80px 10px 10px;
margin: 0;
text-align: left;
color: #24292e;
font-size: 16px;
white-space: nowrap;
.rg-header-selected {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
font-size: 14px;
max-width: 310px;
display: inline-block;
}
}
button {
position: absolute;
-webkit-appearance: none;
padding: 0;
cursor: pointer;
background: 0 0;
border: 0;
outline: none;
color: #999;
top: 9px;
i {
font-size: 16px;
}
&:hover {
color: black;
}
&.rg-removeall-button {
right: 32px
}
&.rg-done-button {
right: 8px;
}
}
}
.rg-level-tabs {
margin-top: 10px;
background-color: #fff;
ul {
padding: 0;
margin: 0;
line-height: 1.5;
border-bottom: 1px solid #eee;
li {
display: inline-block;
position: relative;
a {
display: block;
padding: 0.2rem 1rem 0.6rem;
font-size: 14px;
color: #bbb;
text-decoration: none;
cursor: pointer;
line-height: 1.43;
}
&.active {
a {
color: #333;
background-color: #fff;
font-weight: 600;
}
&::after {
content: "";
display: block;
position: absolute;
bottom: 0;
height: 0.2rem;
width: 100%;
background-color: #bbb;
}
}
}
}
}
.rg-results-container {
background-color: #fff;
list-style: none;
margin: 0;
padding: 0;
position: relative;
width: 100%;
overflow-y: auto;
overflow-x: hidden;
clear: both;
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
.rg-results {
background-color: #fff;
list-style: none;
margin: 0;
padding: 5px;
width: 364px;
line-height: 1.5;
li {
margin: 0;
overflow: hidden;
padding: 3px 10px;
position: relative;
text-align: left;
white-space: nowrap;
font-size: 14px;
color: black;
cursor: pointer;
&.rg-item {
display: inline-block;
border-radius: 2px;
margin-right: 5px;
color: #777;
&:hover {
color: black;
background-color: #F5F5F5;
}
&.active {
background-color: #E4EAEE;
color: black;
}
}
&.rg-message-box {
height: 30px;
line-height: 30px;
text-align: center;
box-sizing: content-box;
font-size: 14px;
cursor: default;
}
}
}
}
.rg-input {
display: block;
background-color: #f5f5f5;
margin: 0 !important;
border: 0;
width: 100%;
font-size: 14px;
line-height: 1.42;
padding: 4px 6px;
vertical-align: middle;
box-sizing: border-box;
outline: none !important;
border-radius: 2px;
&::-webkit-input-placeholder {
color: #999;
}
}

@ -1,4 +1,5 @@
<script type="text/jsx">
import store from '@/store'
import DialogForm from "@/components/DialogForm"
import {createLimitTree, getNodeId} from "@/utils/tree"
@ -11,7 +12,7 @@
visible: false,
handler: null,
limitTree: [],
getChildrenOnClick: false,
getChildrenOnSelect: false,
limit: false,
limitApi: null
}
@ -19,7 +20,7 @@
computed: {
regionTree() {
return this.$store.state.dataCache.regionTree
return store.state.dataCache.regionTree
}
},
@ -30,8 +31,8 @@
nodeClick(obj) {
const payload = [obj]
if (this.getChildrenOnClick) {
let ids = getNodeId(obj.children)
if (this.getChildrenOnSelect) {
const ids = getNodeId(obj.children)
ids.unshift(obj.id)
payload.push(ids)
}
@ -44,7 +45,7 @@
init() {
this.loading = true
const hasInit = this.regionTree.length > 0
const promise = () => hasInit ? Promise.resolve() : this.$store.dispatch('dataCache/initRegion')
const promise = () => hasInit ? Promise.resolve() : store.dispatch('dataCache/initRegion')
return promise()
.then(() => this.limit ? this.limitApi() : Promise.resolve())
.then(data => data && (this.limitTree = createLimitTree(this.regionTree, data)))

@ -1,6 +1,7 @@
<script type="text/jsx">
import Vue from 'vue'
import TreeDialog from "./TreeDialog"
import common from '../mixin'
const TreeDialogConstructor = Vue.extend(TreeDialog)
@ -9,14 +10,7 @@
export default {
name: "RegionTreeSelector",
props: {
value: String,
readonly: Boolean,
size: String,
getChildrenOnClick: Boolean,
limit: Boolean,
limitApi: Function
},
mixins: [common],
data() {
return {
@ -43,7 +37,7 @@
initLimit() {
if (limit) {
limit.getChildrenOnClick = this.getChildrenOnClick
limit.getChildrenOnSelect = this.getChildrenOnSelect
limit.limit = this.limit
limit.limitApi = this.limitApi
return limit.init()
@ -58,10 +52,10 @@
initFull() {
if (full) {
return full.getChildrenOnClick = this.getChildrenOnClick
return full.getChildrenOnSelect = this.getChildrenOnSelect
}
const data = {getChildrenOnClick: this.getChildrenOnClick, handler: this.handler}
const data = {getChildrenOnSelect: this.getChildrenOnSelect, handler: this.handler}
full = new TreeDialogConstructor({data}).$mount()
document.body.appendChild(full.$el)

@ -0,0 +1,18 @@
<script>
import Tree from './Tree'
import Tab from './Tab'
export default {
name: "RegionSelector",
functional: true,
props: {
type: {type: String, default: 'tab', validator: v => ['tree', 'tab'].includes(v)}
},
render(h, context) {
return h(context.props.type === 'tree' ? Tree : Tab, context.data)
}
}
</script>

@ -0,0 +1,10 @@
export default {
props: {
value: String,
readonly: Boolean,
size: String,
getChildrenOnSelect: Boolean,
limit: Boolean,
limitApi: Function
}
}

@ -76,6 +76,12 @@ const router = {
name: 'signatureExample',
component: () => import('@/views/example/components/signatureExample'),
meta: {title: '手写签名'}
},
{
path: 'regionSelectorExample',
name: 'regionSelectorExample',
component: () => import('@/views/example/components/regionSelectorExample'),
meta: {title: '行政区划选择'}
}
]
},

@ -57,7 +57,7 @@ export function isString(str) {
export function isInteger(v) {
let t = parseFloat(v)
return t.toString() !== 'Nan' && t < 2147483647 && t > -2147483648
return t.toString() !== 'NaN' && t < 2147483647 && t > -2147483648
}
export function isImage(str) {

@ -0,0 +1,41 @@
<template>
<div style="width: 500px">
<div class="tip-row">
行政区划选择<a href="https://github.com/modood/Administrative-divisions-of-China" target="_blank">数据由此获取</a>
</div>
<div class="tip-row">
<p>树形</p>
<div>
<region-selector :value="value" type="tree" @clear="clear" @select="select"/>
</div>
</div>
<div class="tip-row">
<p>选项卡型支持v-model<a href="https://github.com/TerryZ/v-region" target="_blank">样式搬的这个</a></p>
<div>
<region-selector v-model="value" @clear="clear"/>
</div>
</div>
</div>
</template>
<script>
import RegionSelector from '@/components/RegionSelector'
export default {
name: "regionSelectorExample",
components: {RegionSelector},
data() {
return {
value: '北京市'
}
},
methods: {
clear() {
this.value = null
},
select(obj) {
this.value = obj.fullname
}
}
}
</script>

@ -156,7 +156,7 @@
exportExcel(baseUrl + '/export', this.mergeSearchForm(), this.excel)
},
nodeClick(obj) {
let ids = getNodeId(obj.children)
const ids = getNodeId(obj.children)
ids.unshift(obj.id)
this.searchForm.cids = ids.join(',')
this.temp.cname = obj.name

@ -14,7 +14,6 @@
<el-form-item label="行政区域:" prop="region_name">
<region-selector
:value="form.region_name"
get-children-on-click
:readonly="!canEdit"
@clear="clearRegion"
@select="selectRegion"
@ -55,7 +54,7 @@
<script>
import DialogForm from '@/components/DialogForm'
import RegionSelector from '@/components/RegionSelector/Tree'
import RegionSelector from '@/components/RegionSelector'
import dialogMixin from "@/mixins/dialogMixin"
import {addCustomer, updateCustomer} from "@/api/system/customer"
import {isEmpty, mergeObj, resetObj} from '@/utils'

@ -9,7 +9,7 @@
:value="temp.region_name"
limit
:limit-api="getLimitRegion"
get-children-on-click
get-children-on-select
@clear="clearSidSearch"
@select="selectRegion"
/>
@ -79,7 +79,7 @@
<script>
import SearchForm from "@/components/SearchForm"
import SearchFormItem from "@/components/SearchForm/SearchFormItem"
import RegionSelector from "@/components/RegionSelector/Tree"
import RegionSelector from "@/components/RegionSelector"
import EditDialog from './EditDialog'
import {delCustomer, getCustomers, getLimitRegion} from "@/api/system/customer"
import {isEmpty} from '@/utils'

@ -14,7 +14,6 @@
<el-form-item label="行政区域:" prop="region_name">
<region-selector
:value="form.region_name"
get-children-on-click
:readonly="!canEdit"
@clear="clearRegion"
@select="selectRegion"

@ -9,7 +9,7 @@
:value="temp.region_name"
limit
:limit-api="getLimitRegion"
get-children-on-click
get-children-on-select
@clear="clearSidSearch"
@select="selectRegion"
/>

Loading…
Cancel
Save