Browse Source

优化

master
longjieli 1 month ago
parent
commit
27b9ac2fd0
16 changed files with 12178 additions and 0 deletions
  1. BIN
      components/.DS_Store
  2. BIN
      components/sn-swiper/.DS_Store
  3. +251
    -0
      components/sn-swiper/esc-swiper-item/index.vue
  4. +21
    -0
      components/sn-swiper/esc-swiper/helper.js
  5. +198
    -0
      components/sn-swiper/esc-swiper/index.vue
  6. +303
    -0
      components/sn-swiper/esc-swiper/mixins/base.mixin.js
  7. +91
    -0
      components/sn-swiper/esc-swiper/mixins/bindingx.js
  8. +143
    -0
      components/sn-swiper/esc-swiper/mixins/index.wxs
  9. +62
    -0
      components/sn-swiper/esc-swiper/mixins/mpother.js
  10. +7
    -0
      components/sn-swiper/esc-swiper/mixins/mpwxs.js
  11. +15
    -0
      components/utils/style.js
  12. +22
    -0
      uni_modules/piaoyi-cityPicker/changelog.md
  13. +10661
    -0
      uni_modules/piaoyi-cityPicker/components/piaoyi-cityPicker/cityData.js
  14. +281
    -0
      uni_modules/piaoyi-cityPicker/components/piaoyi-cityPicker/piaoyi-cityPicker.vue
  15. +17
    -0
      uni_modules/piaoyi-cityPicker/package.json
  16. +106
    -0
      uni_modules/piaoyi-cityPicker/readme.md

BIN
components/.DS_Store View File


BIN
components/sn-swiper/.DS_Store View File


+ 251
- 0
components/sn-swiper/esc-swiper-item/index.vue View File

@ -0,0 +1,251 @@
<template>
<view class="swiper-item" ref="swiper_item" :style="itemStyle">
<view class="item-cntent" bubble="true" @click.stop="onClick"><slot></slot></view>
</view>
</template>
<script>
import { getStyleStr } from '../../utils/style.js';
// #ifdef APP-NVUE
const animation = uni.requireNativePlugin('animation');
// #endif
/**
* esc-swiper-item
* @description esc-swiper-item (不支持使用class)
* @property {Number} index 索引(必填)
* @property {Boolean} clickAny 可以点击任意项
* @event {Function} click 点击事件
*/
export default {
name: 'esc-swiper-item',
inject: ['config'],
props: {
index: {
type: Number,
default: 0
},
clickAny: {
type: Boolean,
default: false
}
},
data() {
return {
isAnimated: false,
timingFunction: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
duration: 0,
current: 0,
position: 0,
mScale: 0,
canClick: true
};
},
created() {},
computed: {
size() {
return this.config.size;
},
width() {
return this.config.width;
},
height() {
return this.config.height;
},
itemWidth() {
return this.config.itemWidth;
},
itemHeight() {
return this.config.itemHeight;
},
space() {
return this.config.space;
},
itemStyle() {
if (this.index == this.size - 1) {
return this.rightSpaceStyle();
} else if (this.index == this.size - 2) {
return this.centerSpaceStyle();
} else {
return this.leftSpaceStyle();
}
},
scale() {
if (!this.config.is3D) {
return 1;
}
if (this.myCurrent == this.current) {
return this.mScale || this.config.scale;
} else {
return this.mScale || 1;
}
},
// swiper-itemcurrent
myCurrent() {
if (!this.config.isCircular) {
return this.index;
}
const p = this.index;
const plus = this.config.plus;
const actSize = this.size - plus * 2;
let current = 0;
if (p < plus) {
current = p + (actSize - plus);
} else if (p >= this.size - plus) {
current = p - (actSize + plus);
} else {
current = p - plus;
}
return current;
}
},
methods: {
itemSize() {
let style = {
width: this.itemWidth + 'rpx',
height: (this.itemHeight || this.height) + 'rpx'
};
if (this.config.is3D) {
// #ifndef APP-NVUE
if (this.isAnimated) {
style.transition = 'transform ' + this.duration + 'ms ' + this.timingFunction;
} else {
style.transition = '';
}
style.transform = 'scale(' + this.scale + ')';
// #endif
// #ifdef APP-NVUE
const isIOS = uni.getSystemInfoSync().platform == 'ios';
if (isIOS) {
style.transform = 'scale(' + this.scale + ')';
} else {
if (!this.isAnimated) style.transform = 'scale(' + this.scale + ')';
}
// #endif
}
return style;
},
leftSpaceStyle() {
return getStyleStr({
...this.itemSize(),
marginLeft: this.space + 'rpx'
});
},
centerSpaceStyle() {
return getStyleStr({
...this.itemSize(),
marginLeft: this.space + 'rpx',
marginRight: this.space + 'rpx'
});
},
rightSpaceStyle() {
return getStyleStr({
...this.itemSize(),
marginRight: this.space + 'rpx'
});
},
onClick(e) {
if (!this.canClick) {
return;
}
// #ifdef APP-NVUE
e.stopPropagation();
// #endif
//
if (this.clickAny) {
this.$emit('click', e);
return;
}
//
if (this.myCurrent == this.current) {
this.$emit('click', e);
}
},
restoreScale(duration) {
if (!this.config.is3D) {
return;
}
// #ifndef APP-NVUE
this.duration = duration;
this.isAnimated = true;
this.mScale = 0;
setTimeout(() => {
this.duration = 0;
this.isAnimated = false;
}, duration);
// #endif
// #ifdef APP-NVUE
this.isAnimated = true;
this.mScale = 0;
animation.transition(
this.$refs['swiper_item'].ref,
{
styles: {
transform: 'scale(' + this.scale + ')'
},
duration, //ms
timingFunction: this.timingFunction,
needLayout: false,
delay: 0 //ms
},
() => {
this.isAnimated = false;
}
);
// #endif
},
restoreToScale(scale, duration) {
if (!this.config.is3D) {
return;
}
// #ifndef APP-NVUE
this.duration = duration;
this.isAnimated = true;
this.mScale = scale;
setTimeout(() => {
this.duration = 0;
this.isAnimated = false;
this.mScale = 0;
}, duration);
// #endif
// #ifdef APP-NVUE
this.isAnimated = true;
animation.transition(
this.$refs['swiper_item'].ref,
{
styles: {
transform: 'scale(' + scale + ')'
},
duration, //ms
timingFunction: this.timingFunction,
needLayout: false,
delay: 0 //ms
},
() => {
this.isAnimated = false;
this.mScale = 0;
}
);
// #endif
}
}
};
</script>
<style scoped>
.swiper-item {
position: relative;
}
.item-cntent {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
}
</style>

+ 21
- 0
components/sn-swiper/esc-swiper/helper.js View File

@ -0,0 +1,21 @@
/**
* getSwiperList
* @description 获取Swiper数据
* @param {Array} list 原数据
* @param {Object} options 配置
* @param {Boolean} options.circular 是否循环
* @param {Number} options.plus 左右追加个数(开启循环必填至少为2)
* @return {Array}
*/
export function getSwiperList(list, options = {
circular: true,
plus: 2
}) {
if (!options.circular) {
return list
}
const plus = options.plus || 2
const leftPlusList = [...list].reverse().slice(0, plus).reverse();
const rightPlusList = [...list].slice(0, plus);
return [].concat(leftPlusList).concat(list).concat(rightPlusList);
}

+ 198
- 0
components/sn-swiper/esc-swiper/index.vue View File

@ -0,0 +1,198 @@
<template>
<!-- #ifdef APP-VUE || H5 -->
<view class="box" :style="boxStyle">
<view
ref="container"
class="container"
:change:prop="swipe.changeData"
:prop="wxsData"
@touchstart.stop="swipe.touchstart"
@touchmove.stop="swipe.touchmove"
@touchend.stop="swipe.touchend"
:style="containerStyle"
>
<slot></slot>
</view>
</view>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<view class="box" :style="boxStyle">
<view
ref="container"
class="container"
:change:prop="swipe.changeData"
:prop="wxsData"
@touchstart="swipe.touchstart"
@touchmove="swipe.touchmove"
@touchend="swipe.touchend"
:style="containerStyle"
>
<slot></slot>
</view>
</view>
<!-- #endif -->
<!-- #ifdef APP-NVUE -->
<view class="box" :style="boxStyle">
<view ref="container" class="container" @horizontalpan="touchstart" :style="containerStyle"><slot></slot></view>
</view>
<!-- #endif -->
<!-- 其他平台使用 js 长列表性能可能会有影响-->
<!-- #ifndef APP-PLUS || MP-WEIXIN || H5 -->
<view class="box" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend" :style="boxStyle">
<view ref="container" class="container" :style="containerStyle"><slot></slot></view>
</view>
<!-- #endif -->
</template>
<script src="./mixins/index.wxs" module="swipe" lang="wxs"></script>
<script>
import SwiperMixin from './mixins/base.mixin.js';
// #ifdef APP-VUE || MP-WEIXIN || H5
import MpMixin from './mixins/mpwxs.js';
// #endif
// #ifdef APP-NVUE
import BindingxMixin from './mixins/bindingx.js';
// #endif
// #ifndef APP-PLUS || MP-WEIXIN || H5
import OtherMixin from './mixins/mpother.js';
// #endif
const mixins = [
// #ifdef APP-VUE || MP-WEIXIN || H5
MpMixin,
// #endif
// #ifdef APP-NVUE
BindingxMixin,
// #endif
// #ifndef APP-PLUS || MP-WEIXIN || H5
OtherMixin,
// #endif
SwiperMixin
];
/**
* esc-swiper
* @description 自定义swiper (不支持使用class)
* @property {String} mode = [normal|3d] 模式
* @property {Number} scale 3D模式选中项的scale
* @property {Number} width
* @property {Number} height
* @property {Number} itemWidth 项宽
* @property {Number} itemHeight 项高
* @property {Number} space 间距
* @property {Number} plus 左右追加个数(开启循环必填至少为2)
* @property {Number} current 选中项索引
* @property {Boolean} autoplay 自动轮播
* @property {Boolean} circular 是否循环,如果开启至少需要3项
* @property {Boolean} bounce 阻尼效果
* @event {Function} change 索引变化
*/
export default {
name: 'esc-swiper',
mixins,
props: {
mode: {
type: String,
default: 'normal'
},
scale: Number,
width: Number,
height: Number,
itemWidth: {
type: Number,
default: 0
},
itemHeight: {
type: Number,
default: 0
},
space: {
type: Number,
default: 0
},
plus: {
type: Number,
default: 0
},
autoplay: {
type: Boolean,
default: false
},
current: {
type: Number,
default: 0
},
interval: {
type: Number,
default: 5000
},
duration: {
type: Number,
default: 500
},
circular: {
type: Boolean,
default: false
},
bounce: {
type: Boolean,
default: true
},
size: {
type: Number,
default: 0
}
},
provide() {
return {
config: {
is3D: this.is3D,
isCircular: this.circular,
scale: this.scale,
size: this._size,
width: this.width,
height: this.height,
itemWidth: this.itemWidth,
itemHeight: this.itemHeight,
space: this.space,
plus: this.plus
}
};
},
mounted() {
if (this.autoplay) {
this.autoplayInterval = setInterval(() => {
this.next();
}, this.interval);
}
},
watch: {
autoplay(newV) {
if (!newV) {
clearInterval(this.autoplayInterval);
} else {
this.autoplayInterval = setInterval(() => {
this.next();
}, this.interval);
}
}
},
methods: {}
};
</script>
<style scoped>
.box {
position: relative;
overflow: hidden;
}
.container {
position: absolute;
top: 0;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
}
</style>

+ 303
- 0
components/sn-swiper/esc-swiper/mixins/base.mixin.js View File

@ -0,0 +1,303 @@
import {
getStyleStr
} from '../../../utils/style.js';
// #ifdef APP-NVUE
const animation = uni.requireNativePlugin('animation')
// #endif
export default {
data() {
return {
timingFunction: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
isAnimated: false,
isScrolling: false,
customDuration: 0,
left: 0,
mCurrent: this.current
};
},
created() {},
mounted() {
// #ifdef MP
this.swiperViews = this.$children
// #endif
// #ifdef APP-PLUS || H5
this.swiperViews = this.$slots.default.map(it => it.child)
// #endif
this._setLeft();
this.mCurrent = this.current
this._notifyCurrentForItems(this.current, this.position)
},
watch: {
mCurrent() {
let current = this.mCurrent
if (this.circular) {
if (this.position == 1) {
current = this.actualSize - (this.plus - 1)
// console.log('最前了', current)
} else if (this.position == this._size - 2) {
current = this.plus - 2;
// console.log('最后了', current)
}
if (current < 0) {
current = this.position + 1
}
current %= this.actualSize
}
// console.log('position', this.position, current)
this.$emit('update:current', current)
this.$emit('change', current)
this._notifyCurrentForItems(current, this.position)
}
},
computed: {
is3D() {
return this.mode == '3d'
},
position() {
return this.circular ? (this.mCurrent + this.plus) : this.mCurrent
},
manualDuration() {
if (this.customDuration > 0)
return this.customDuration
return this.isAnimated ? this.duration : 0
},
boxStyle() {
return getStyleStr({
width: this.width + 'rpx',
height: this.height + 'rpx'
});
},
containerStyle() {
const style = {
height: this.height + 'rpx'
};
// #ifdef APP-NVUE
// FIXME: 理论isAnimated=false应该不设置transform,但是ios有个奇怪的问题,top不为0导致布局错位
const isIOS = uni.getSystemInfoSync().platform == 'ios'
if (isIOS) {
style.transform = 'translate(' + uni.upx2px(this.left) + 'px' + ',0px)'
} else {
if (this.isAnimated == false) {
style.transform = 'translate(' + uni.upx2px(this.left) + 'px' + ',0px)'
}
}
// #endif
// #ifndef APP-NVUE
style.left = this.left + 'rpx'
style.transition = 'left ' + this.manualDuration + 'ms ' + this.timingFunction
// #endif
return getStyleStr(style);
},
_size() {
return (this.$slots && this.$slots.default && this.$slots.default.length) || this.size;
},
// plus * 2
plusSize() {
return this.circular ? this.plus * 2 : 0;
},
// 真实长度
actualSize() {
return this._size - this.plusSize;
}
},
methods: {
prev() {
if (this.isAnimated) return;
if (this.isScrolling) return;
if (this.mCurrent == 0 && this.circular == false) return
this.mCurrent--;
this._run()
},
next() {
if (this.isAnimated) return;
if (this.isScrolling) return;
if (this.circular == true) {
this.mCurrent++;
if (this.mCurrent == this._size) {
this.mCurrent = 0;
}
} else {
if (this.mCurrent == this._size - 1) return
this.mCurrent++;
}
this._run()
},
moveTo(e) {
if (this.isAnimated) return
const {
deltaX,
left
} = e
this.isScrolling = true
if (!this.circular) {
if (
// 第一项,不能向右滑(上一项)
(deltaX > 0 && this.mCurrent == 0) ||
// 最后一项,不能向左滑(下一项)
(deltaX < 0 && this.mCurrent == this._size - 1)
) {
if (!this.bounce) return
// 添加阻尼滑动
const _left = this._left || this.wxsData.left
this.left = _left + (deltaX * (1 - Math.abs(deltaX) * 3 / (this.width * 5)))
this._set3DScale(deltaX)
return
}
}
this.left = left
this._set3DScale(deltaX)
},
moveEnd(e) {
const {
velocity,
deltaX,
deltaY
} = e
this.isScrolling = false
if (!this.circular) {
// 第一项,不能向右滑(上一项)
if (deltaX > 0 && this.mCurrent == 0) {
this._restoreStartTouch()
return
}
// 最后一项,不能向左滑(下一项)
if (deltaX < 0 && this.mCurrent == this._size - 1) {
this._restoreStartTouch()
return
}
}
const isTurnPage = Math.abs(deltaX) > this.itemWidth / 2
if (isTurnPage || velocity > 0.2) {
if (deltaX < 0) {
this.customDuration = 350
this.next()
} else if (deltaX > 0) {
this.customDuration = 350
this.prev()
}
} else {
this._restoreStartTouch()
}
},
_set3DScale(deltaX) {
if (this.is3D) {
const min = Math.min
const maxScale = Math.abs(this.scale - 1)
const mScale = deltaX * maxScale / this.width
const mRealScale = min(this.scale, this.scale - Math.abs(mScale))
this.swiperViews[this.position].mScale = mRealScale < 1 ? 1 : mRealScale
if (this.position - 1 > -1) {
this.swiperViews[this.position - 1].mScale = mScale > 0 ? min(this.scale, 1 + mScale) : min(1, 1 + mScale)
}
if (this.position + 1 < this._size) {
this.swiperViews[this.position + 1].mScale = mScale > 0 ? min(1, 1 - mScale) : min(this.scale, 1 - mScale)
}
}
},
_restoreStartTouch() {
const self = this
this.customDuration = 300
// #ifdef APP-VUE || MP-WEIXIN || H5
this.left = this.wxsData.left
// #endif
// #ifndef APP-PLUS || MP-WEIXIN || H5
this.left = this._left
// #endif
this._run(false)
if (this.is3D) {
this.swiperViews[this.position].restoreScale(this.manualDuration)
if (this.position - 1 > -1) {
this.swiperViews[this.position - 1].restoreScale(this.manualDuration)
}
if (this.position + 1 < this._size) {
this.swiperViews[this.position + 1].restoreScale(this.manualDuration)
}
}
},
_notifyCurrentForItems(current, position) {
this.swiperViews && this.swiperViews.forEach(it => {
it.current = current
it.position = position
})
},
_run(isSet = true) {
this.isAnimated = true;
if (isSet)
this._setLeft();
const self = this;
if (this.is3D) {
this.swiperViews[this.position].restoreToScale(this.scale, this.manualDuration)
if (this.position - 1 > -1) {
this.swiperViews[this.position - 1].restoreToScale(1, this.manualDuration)
}
if (this.position + 1 < this._size) {
this.swiperViews[this.position + 1].restoreToScale(1, this.manualDuration)
}
}
// #ifdef APP-NVUE
animation.transition(this.$refs.container, {
styles: {
transform: 'translate(' + uni.upx2px(this.left) + 'px' + ',0px)',
},
duration: this.manualDuration, //ms
timingFunction: this.timingFunction,
needLayout: false,
delay: 0 //ms
}, function() {
self._reset();
})
// #endif
// #ifndef APP-NVUE
setTimeout(() => {
this._reset();
}, this.manualDuration);
// #endif
},
_setLeft() {
if (this.circular == true) {
const s1 = (this.width - this.itemWidth - this.space * 2) / 2;
let left = (this.plus + this.mCurrent) * (this.space + this.itemWidth) - s1;
this.left = -left;
} else {
this.left = -(this.itemWidth + this.space) * this.mCurrent
}
// #ifdef APP-VUE || MP-WEIXIN || H5
this.wxsData = {
left: this.left,
bounce: this.bounce
}
// #endif
},
_reset() {
this.isScrolling = false
this.isAnimated = false
this.customDuration = 0
if (this.circular == true) {
if (this.position == 1) {
this.mCurrent = this.actualSize - (this.plus - 1);
this._setLeft();
this._restoreScale()
}
// -2:原数组索引-1 + plus数组索引-1
if (this.position == this._size - 2) {
this.mCurrent = this.plus - 2;
this._setLeft();
this._restoreScale()
}
}
},
_restoreScale() {
if (this.is3D) {
this.swiperViews[this.position].restoreToScale(this.scale, 0)
if (this.position - 1 > -1) {
this.swiperViews[this.position - 1].restoreToScale(1, 0)
}
if (this.position + 1 < this._size) {
this.swiperViews[this.position + 1].restoreToScale(1, 0)
}
}
}
}
}

+ 91
- 0
components/sn-swiper/esc-swiper/mixins/bindingx.js View File

@ -0,0 +1,91 @@
const BindingX = uni.requireNativePlugin('bindingx');
const animation = uni.requireNativePlugin('animation');
export default {
mounted() {
this.container = this.getEl(this.$refs['container'])
},
methods: {
_notifyTouchChangeForItems(isTouchEnd) {
this.swiperViews && this.swiperViews.forEach(it => {
it.canClick = isTouchEnd
})
},
touchstart(e) {
if (this.isAnimated) return;
if (this.stop) return
this.isScrolling = true
this._notifyTouchChangeForItems(false)
this.stop = true
this.startTime = new Date().getTime()
const props = [{
element: this.container,
property: 'transform.translateX',
expression: uni.upx2px(this.left) + '+x'
}]
if (this.is3D) {
const deltaScale = `${this.scale}/${this.width}*x`
const currentScale = `${this.scale}-abs(x)/${this.width}*1`
const realScale = `min(${currentScale},${this.scale})`
const currentCardExp = `${realScale} < 1 ? 1 : ${realScale}`;
const leftCardExp = `${deltaScale} > 0 ? min(${this.scale},(1 + ${deltaScale})) : min(1,(1 + ${deltaScale}))`;
const rightCardExp = `${deltaScale} > 0 ? min(1,(1 - ${deltaScale})) : min(${this.scale},(1 - ${deltaScale}))`;
props.push({
element: this.getSwipteItemEl(this.position),
property: 'transform.scale',
expression: currentCardExp
})
if (this.position - 1 > -1) {
props.push({
element: this.getSwipteItemEl(this.position - 1),
property: 'transform.scale',
expression: leftCardExp
})
}
if (this.position + 1 > this._size) {
props.push({
element: this.getSwipteItemEl(this.position + 1),
property: 'transform.scale',
expression: rightCardExp
})
}
}
this.eventpan = BindingX.bind({
anchor: this.container,
eventType: 'pan',
props
}, (e) => {
if (e.state === 'end' ||
e.state === 'exit') {
setTimeout(() => {
this._notifyTouchChangeForItems(true)
}, 10)
const timing = new Date().getTime() - this.startTime
const velocity = Math.abs(e.deltaX / timing)
this.stop = false
this.isScrolling = false
if (this.eventpan && this.eventpan.token) {
BindingX.unbind({
token: this.eventpan.token,
eventType: 'pan'
})
}
this.moveEnd({
velocity,
deltaX: e.deltaX,
deltaY: e.deltaY
})
}
})
},
getSwipteItemEl(idx) {
return this.swiperViews[idx].$el.ref
},
/**
* 获取ref
* @param {Object} el
*/
getEl(el) {
return el.ref
}
}
}

+ 143
- 0
components/sn-swiper/esc-swiper/mixins/index.wxs View File

@ -0,0 +1,143 @@
var MIN_DISTANCE = 10;
function changeData(newValue, oldValue, ownerInstance, instance) {
var state = instance.getState();
if (newValue.left != undefined) {
state.left = newValue.left
}
if (newValue.bounce != undefined) {
state.bounce = newValue.bounce
}
// console.log('changeData', JSON.stringify(newValue))
}
/**
* 开始触摸操作
* @param {Object} e
* @param {Object} ins
*/
function touchstart(e, ins) {
var instance = e.instance;
// 计算滑动开始位置
stopTouchStart(e, ins)
}
/**
* 开始滑动操作
* @param {Object} e
* @param {Object} ownerInstance
*/
function touchmove(e, ownerInstance) {
var instance = e.instance;
// 是否可以滑动页面
stopTouchMove(e);
if (e.preventDefault) {
// 阻止页面滚动
e.preventDefault()
}
// var state = instance.getState();
// && state.bounce
move(instance, ownerInstance)
return false
}
/**
* 结束触摸操作
* @param {Object} e
* @param {Object} ownerInstance
*/
function touchend(e, ownerInstance) {
var instance = e.instance;
var state = instance.getState()
ownerInstance.callMethod('moveEnd', {
velocity: Math.abs(state.deltaX / state.timing),
direction: state.direction,
deltaX: state.deltaX,
deltaY: state.deltaY
})
}
/**
* 设置移动距离
* @param {Object} instance
* @param {Object} ownerInstance
*/
function move(instance, ownerInstance) {
var state = instance.getState()
var value = state.deltaX || 0
var state = instance.getState()
if (state.direction == 'horizontal') {
// instance.requestAnimationFrame(function() {
// instance.setStyle({
// transform: 'translateX(' + value + 'px)',
// '-webkit-transform': 'translateX(' + value + 'px)'
// })
// })
ownerInstance.callMethod('moveTo', {
deltaX: value,
deltaY: state.deltaY || 0,
left: state.left + value
})
}
}
/**
* 滑动中,是否禁止打开
* @param {Object} event
*/
function stopTouchMove(event) {
var instance = event.instance;
var state = instance.getState();
var touch = event.touches[0];
state.timing = getDate().getTime() - state.startTime;
state.deltaX = touch.clientX - state.startX;
state.deltaY = touch.clientY - state.startY;
state.offsetX = Math.abs(state.deltaX);
state.offsetY = Math.abs(state.deltaY);
state.direction = state.direction || getDirection(state.offsetX, state.offsetY);
}
/**
* 设置滑动开始位置
* @param {Object} event
*/
function stopTouchStart(event) {
var instance = event.instance;
var state = instance.getState();
resetTouchStatus(instance);
var touch = event.touches[0];
state.startTime = getDate().getTime();
state.startX = touch.clientX;
state.startY = touch.clientY;
}
function getDirection(x, y) {
if (x > y && x > MIN_DISTANCE) {
return 'horizontal';
}
if (y > x && y > MIN_DISTANCE) {
return 'vertical';
}
return '';
}
/**
* 重置滑动状态
* @param {Object} event
*/
function resetTouchStatus(instance) {
var state = instance.getState();
state.direction = '';
state.deltaX = 0;
state.deltaY = 0;
state.offsetX = 0;
state.offsetY = 0;
}
module.exports = {
changeData: changeData,
touchstart: touchstart,
touchmove: touchmove,
touchend: touchend
}

+ 62
- 0
components/sn-swiper/esc-swiper/mixins/mpother.js View File

@ -0,0 +1,62 @@
const MIN_DISTANCE = 10;
export default {
methods: {
touchstart(e) {
this._left = this.left
this.stopTouchStart(e)
},
touchmove(e) {
// 是否可以滑动页面
this.stopTouchMove(e);
if (this.direction == 'horizontal') {
this.moveTo({
deltaX: this.deltaX,
deltaY: this.deltaY || 0,
left: this._left + this.deltaX
})
}
// FIXME: 冒泡
return false
},
touchend() {
this.moveEnd({
velocity: Math.abs(this.deltaX / this.timing),
direction: this.direction,
deltaX: this.deltaX,
deltaY: this.deltaY,
})
},
stopTouchStart(event) {
this.resetTouchStatus();
const touch = event.touches[0];
this.startTime = new Date().getTime();
this.startX = touch.clientX;
this.startY = touch.clientY;
},
stopTouchMove(event) {
const touch = event.touches[0];
this.timing = new Date().getTime() - this.startTime;
this.deltaX = touch.clientX - this.startX;
this.deltaY = touch.clientY - this.startY;
this.offsetX = Math.abs(this.deltaX);
this.offsetY = Math.abs(this.deltaY);
this.direction = this.direction || this.getDirection(this.offsetX, this.offsetY);
},
getDirection(x, y) {
if (x > y && x > MIN_DISTANCE) {
return 'horizontal';
}
if (y > x && y > MIN_DISTANCE) {
return 'vertical';
}
return '';
},
resetTouchStatus() {
this.direction = '';
this.deltaX = 0;
this.deltaY = 0;
this.offsetX = 0;
this.offsetY = 0;
}
}
}

+ 7
- 0
components/sn-swiper/esc-swiper/mixins/mpwxs.js View File

@ -0,0 +1,7 @@
export default {
data() {
return {
wxsData: {}
}
}
}

+ 15
- 0
components/utils/style.js View File

@ -0,0 +1,15 @@
const toLine = (name) => {
return name.replace(/([A-Z])/g, '-$1').toLowerCase();
}
/**
* style对象转化为style字符串
* @return {string}
*/
export const getStyleStr = (styleObject) => {
let transfrom = '';
for (let i in styleObject) {
let line = toLine(i);
transfrom += line + ':' + styleObject[i] + ';';
}
return transfrom
}

+ 22
- 0
uni_modules/piaoyi-cityPicker/changelog.md View File

@ -0,0 +1,22 @@
## 1.1.7(2024-10-29)
修复底部露出部分组件bug
## 1.1.6(2024-08-20)
更新本地数据源为最新数据源
## 1.1.5(2024-06-12)
使用说明文档优化
## 1.1.4(2024-06-12)
增加问题反馈描述
## 1.1.3(2024-02-29)
更新使用文档
## 1.1.2(2024-01-16)
解决Vue3项目导入导出报错问题
## 1.1.1(2023-12-06)
defaultValue可以传入defaultValue:['河北省','唐山市','丰南区']数组类型以及defaultValue: '420103'地区编码字符串类型
## 1.1.0(2023-12-05)
即默认值传入地区编码,也支持传入中文省市区数组
## 1.0.9(2023-12-04)
优化
## 1.0.8(2023-10-24)
修复东菀市和中山市下各镇的行政编码错误问题
## 1.0.4(2023-09-15)
改为uni_modules规范

+ 10661
- 0
uni_modules/piaoyi-cityPicker/components/piaoyi-cityPicker/cityData.js
File diff suppressed because it is too large
View File


+ 281
- 0
uni_modules/piaoyi-cityPicker/components/piaoyi-cityPicker/piaoyi-cityPicker.vue View File

@ -0,0 +1,281 @@
<template>
<view class="pupop">
<view class="popup-box" :animation="animationData">
<view class="pupop-btn">
<view @tap="cancel">取消</view>
<view @tap="confirm" style="color: #2979ff;">确定</view>
</view>
<picker-view :value="value" :indicator-style="indicatorStyle" @change="bindChange" class="picker-view">
<picker-view-column>
<view class="item" v-for="(item,index) in provinceList" :key="index">{{item.name}}</view>
</picker-view-column>
<picker-view-column>
<view class="item" v-for="(item,index) in cityList" :key="index">{{item.name}}</view>
</picker-view-column>
<picker-view-column v-if="column == 3">
<view class="item" v-for="(item,index) in areaList" :key="index">{{item.name}}</view>
</picker-view-column>
</picker-view>
</view>
<view @tap="close" @touchmove.stop.prevent :class="visible ? 'pupop-model' : 'pupop-models'"></view>
</view>
</template>
<script>
import {
addressList
} from './cityData.js'
export default {
data() {
return {
value: [],
addressList,
provinceList: [],
cityList: [],
areaList: [],
indicatorStyle: `height: 50px;`,
provinceIndex: 0,
cityIndex: 0,
areaIndex: 0,
animationData: {}
}
},
props: {
column: {
type: Number,
default: 3
},
defaultValue: {
default: () => ''
},
visible: {
type: Boolean,
default: () => false
},
maskCloseAble: {
type: Boolean,
default: () => true
},
},
watch: {
visible (val) {
this.changeActive()
},
defaultValue() {
this.init()
}
},
created() {
this.init()
},
methods: {
init () {
var provinceList = []
addressList.filter(item => {
provinceList.push({
code: item.code,
name: item.name
})
})
this.provinceList = [...provinceList]
if (!this.defaultValue) {
this.cityList = addressList[0].children
this.areaList = addressList[0].children[0].children
} else {
var city = {11:"北京",12:"天津",13:"河北",14:"山西",15:"内蒙古",21:"辽宁",22:"吉林",23:"黑龙江 ",31:"上海",32:"江苏",33:"浙江",34:"安徽",35:"福建",36:"江西",37:"山东",41:"河南",42:"湖北 ",43:"湖南",44:"广东",45:"广西",46:"海南",50:"重庆",51:"四川",52:"贵州",53:"云南",54:"西藏 ",61:"陕西",62:"甘肃",63:"青海",64:"宁夏",65:"新疆",71:"台湾",81:"香港",82:"澳门",91:"国外 "}
if (Array.isArray(this.defaultValue) && this.defaultValue.length >= 2) {
console.log(this.defaultValue)
var province = this.defaultValue[0]
var city = this.defaultValue[1]
this.provinceIndex = 0
this.cityIndex = 0
this.areaIndex = 0
this.provinceIndex = addressList.findIndex(obj => {
return obj.name == province
})
this.provinceIndex = this.provinceIndex >= 0 ? this.provinceIndex : 0
this.cityList = addressList[this.provinceIndex].children
this.cityIndex = this.cityList.findIndex(obj => {
return obj.name == city
})
this.cityIndex = this.cityIndex >= 0 ? this.cityIndex : 0
this.areaList = this.cityList[this.cityIndex].children
if (this.defaultValue.length > 2) {
this.areaIndex = this.areaList.findIndex(obj => {
return obj.name == this.defaultValue[2]
})
}
this.areaIndex = this.areaIndex >= 0 ? this.areaIndex : 0
this.$nextTick(() => {
if (this.column == 3) {
this.value = JSON.parse(JSON.stringify([this.provinceIndex, this.cityIndex, this.areaIndex]))
} else if (this.column == 2) {
this.value = JSON.parse(JSON.stringify([this.provinceIndex, this.cityIndex]))
}
})
} else if (/^\d{6}$/.test(this.defaultValue)) {
var province = this.defaultValue.substr(0, 2)
var city = this.defaultValue.substr(0, 4)
this.provinceIndex = 0
this.cityIndex = 0
this.areaIndex = 0
this.provinceIndex = addressList.findIndex(obj => {
return obj.code == province
})
this.provinceIndex = this.provinceIndex >= 0 ? this.provinceIndex : 0
this.cityList = addressList[this.provinceIndex].children
this.cityIndex = this.cityList.findIndex(obj => {
return obj.code == city
})
this.cityIndex = this.cityIndex >= 0 ? this.cityIndex : 0
this.areaList = this.cityList[this.cityIndex].children
this.areaIndex = this.areaList.findIndex(obj => {
return obj.code == this.defaultValue
})
this.areaIndex = this.areaIndex >= 0 ? this.areaIndex : 0
this.$nextTick(() => {
if (this.column == 3) {
this.value = JSON.parse(JSON.stringify([this.provinceIndex, this.cityIndex, this.areaIndex]))
} else if (this.column == 2) {
this.value = JSON.parse(JSON.stringify([this.provinceIndex, this.cityIndex]))
}
})
} else {
uni.showToast({
title: '地区编码格式不正确',
icon: 'none'
})
console.log('地区编码格式不正确')
}
}
this.changeActive()
},
confirm () {
if (this.column == 3) {
this.$emit('confirm', {
code: addressList[this.provinceIndex].children[this.cityIndex].children[this.areaIndex].code,
name: addressList[this.provinceIndex].name + addressList[this.provinceIndex].children[this.cityIndex].name + addressList[this.provinceIndex].children[this.cityIndex].children[this.areaIndex].name,
provinceName: addressList[this.provinceIndex].name,
cityName: addressList[this.provinceIndex].children[this.cityIndex].name,
areaName: addressList[this.provinceIndex].children[this.cityIndex].children[this.areaIndex].name
})
} else if (this.column == 2) {
this.$emit('confirm', {
code: addressList[this.provinceIndex].children[this.cityIndex].children[this.areaIndex].code.substring(0, 4) + '00',
name: addressList[this.provinceIndex].name + addressList[this.provinceIndex].children[this.cityIndex].name,
provinceName: addressList[this.provinceIndex].name,
cityName: addressList[this.provinceIndex].children[this.cityIndex].name
})
} else {
uni.showToast({
title: '目前column只能传2和3',
icon: 'none'
})
}
},
cancel () {
this.$emit('cancel')
},
//
changeActive () {
var active = '-415px'
if (this.visible) {
active = 0
}
var animation = uni.createAnimation({
duration: 400,
timingFunction: 'linear'
})
animation.bottom(active).step()
this.animationData = animation.export()
},
bindChange(e) {
e.detail.value[0] = e.detail.value[0] || 0
e.detail.value[1] = e.detail.value[1] || 0
e.detail.value[2] = e.detail.value[2] || 0
if (e.detail.value[0] != this.provinceIndex) {
// console.log('')
this.provinceChange(e.detail.value[0])
} else if (e.detail.value[1] != this.cityIndex) {
// console.log('')
this.cityChange(e.detail.value[1])
} else if (e.detail.value[2] != this.areaIndex) {
// console.log('')
this.areaChange(e.detail.value[2])
}
},
//
provinceChange (e) {
this.provinceIndex = e
this.cityIndex = 0
this.areaIndex = 0
this.value = [...[e, 0, 0]]
this.cityList = addressList[e].children
this.areaList = addressList[e].children[0].children
},
//
cityChange (e) {
this.cityIndex = e
this.areaIndex = 0
// console.log(this.cityIndex, this.areaIndex)
this.value = [...[this.provinceIndex, e, 0]]
this.cityList = addressList[this.provinceIndex].children
this.areaList = addressList[this.provinceIndex].children[e].children
},
//
areaChange (e) {
this.areaIndex = e
},
//
close () {
if (this.maskCloseAble) {
this.cancel()
}
}
}
}
</script>
<style scoped lang="scss">
.pupop {
.popup-box {
position: fixed;
left: 0;
bottom: -315px;
z-index: 99999;
background: #fff;
padding-bottom: 50px;
.pupop-btn{
height: 40px;
display: flex;
align-items: center;
padding: 0 30upx;
justify-content: space-between;
}
}
.pupop-model {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 9999;
background: rgba(0, 0, 0, .5);
}
.pupop-models{
display: none;
}
.picker-view {
width: 750rpx;
height: 225px;
margin-top: 20rpx;
}
.item {
height: 50px;
align-items: center;
justify-content: center;
text-align: center;
line-height: 50px;
}
}
</style>

+ 17
- 0
uni_modules/piaoyi-cityPicker/package.json View File

@ -0,0 +1,17 @@
{
"id": "piaoyi-cityPicker",
"name": " data-cityPicker省市区地址选择器",
"displayName": " data-cityPicker省市区地址选择器(兼容vue3)",
"version": "1.1.7",
"description": "常用省市区选择器,可反选,自定义模态框、兼容vue3",
"keywords": [
"省市区",
"选择器",
"可反选",
"自定义模态框和弹框",
"兼容vue3"
],
"dcloudext": {
"type": "component-vue"
}
}

+ 106
- 0
uni_modules/piaoyi-cityPicker/readme.md View File

@ -0,0 +1,106 @@
## 1.0.7(2023-09-15)
改为uni_modules规范
### cityPicker 省市区选择器(兼容vue3)
**使用方法:**
```
<template>
<view>
<button @tap="open">打开</button>
<cityPicker :column="column" :default-value="defaultValue" :mask-close-able="maskCloseAble" @confirm="confirm" @cancel="cancel" :visible="visible"/>
<view>{{str}}</view>
</view>
</template>
<script>
import cityPicker from '@/uni_modules/piaoyi-cityPicker/components/piaoyi-cityPicker/piaoyi-cityPicker'
export default {
data() {
return {
visible: false,
maskCloseAble: true,
str: '',
defaultValue: '420103',
// defaultValue: ['河北省','唐山市','丰南区'],
column: 3
}
},
components: {
cityPicker
},
methods: {
open () {
this.visible = true
},
confirm (val) {
console.log(val)
this.str = JSON.stringify(val)
this.visible = false
},
cancel () {
this.visible = false
}
},
onShareAppMessage(res) {
if (res.from === 'button') { // 来自页面内分享按钮
console.log(res.target)
}
return {
title: 'data-cityPicker省市区地址选择器!',
path: '/pages/cityPicker/cityPicker'
}
},
onShareTimeline(res) {
if (res.from === 'button') { // 来自页面内分享按钮
console.log(res.target)
}
return {
title: 'data-cityPicker省市区地址选择器!'
}
}
}
</script>
<style lang="scss">
</style>
```
### 注:近期收到使用用户反馈,存在以下一个问题(如有好的建议,期待私信,谢谢)
1、之前只支持默认值传入地区编码,已更新可以支持传入中文省市区数组
defaultValue可以传入defaultValue:['河北省','唐山市','丰南区']数组类型以及defaultValue: '420103'地区编码字符串类型
可以使用const reg =/([\u4e00-\u9fa5]+省|自治区|[\u4e00-\u9fa5]+市|[\u4e00-\u9fa5]+区)/g;将自己的省市区数据进行处理为数组再传入
2、有些用户反馈vue3下watch监听有问题
我自己创建一个vue项目,导入插件后,按照示例原封不动进行测试,没有发现问题; 发现有问题的朋友可以提供一下可以复现的demo给我,我这边看看具体什么情况
3、有些用户返回无法关闭弹框
不要把插件放到scroll-view里面,请务必放到最外层进行使用
#### 事件说明
| 事件名 | 返回值 | 描述 |
| :---------: | :----: | :------------: |
| @confirm | 对象(code,完整地区名称) | 点击确定的回调 |
| @cancel | 无 | 点击取消的回调 |
#### Prop
| 参数名称 | 描述 |
| -------- | ------------------------------ |
| visible | 控制选择器显示和隐藏 |
| column | 可选值2和3,2是省市两列选择;3是省市区三列选择 |
| maskCloseAble | 点击模态框是否关闭弹框 |
| defaultValue | 初始地区编码(例:420102或者['河北省','唐山市','丰南区']) |
### 数据来源:[点击查看省市区数据来源](https://github.com/modood/Administrative-divisions-of-China/blob/master/dist/pca-code.json)
### 可接定制化组件开发
### 右侧有本人代表作小程序二维码,可以扫码体验
### 如使用过程中有问题或有一些好的建议,欢迎加QQ群互相学习交流:120594820

Loading…
Cancel
Save