new-cashier/jeepay-ui-uapp-agent/components/xp-picker/xp-picker.vue

346 lines
8.1 KiB
Vue
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view>
<view v-if="hasSlot" @tap="show">
<slot />
</view>
<view class="xp-picker" :style="{ visibility: pickerVisible ? 'visible' : 'hidden' }">
<view
class="xp-picker-mask"
:class="{ 'xp-picker-animation': animation }"
:style="{ opacity: pickerVisible ? 0.6 : 0 }"
@tap="_cancel"
></view>
<view
class="xp-picker-container"
:class="{ 'xp-picker-container--show': pickerVisible, 'xp-picker-animation': animation }"
>
<view v-if="actionPosition === 'top'" class="xp-picker-action">
<view class="xp-picker-action--cancel" @tap="_cancel">取消</view>
<view class="xp-picker-action--confirm" @tap="_confirm">确定</view>
</view>
<view v-if="isError" class="xp-picker-error" :style="{ height: height + 'vh' }">
<text>Errorplease check your configuration</text>
<text>(请检查你的配置 或 查看控制台错误信息)</text>
</view>
<picker-view
v-else
:style="{ height: height + 'vh' }"
indicator-style="height:40px;"
:value="selected"
@change="_change"
>
<picker-view-column v-for="(k, i) in modeArr" :key="i" class="xp-picker-column">
<view class="xp-picker-list-item" v-for="(item, index) in cols[i]" :key="index">
{{ item + units[i] }}
</view>
</picker-view-column>
</picker-view>
<view v-if="actionPosition === 'bottom'" class="xp-picker-btns">
<view class="xp-button xp-button--cancel" @tap="_cancel">取消</view>
<view class="xp-button xp-button--confirm" @tap="_confirm">确定</view>
</view>
</view>
</view>
</view>
</template>
<script>
import tool from "@/util/tool.js"
import { templateFactory, getLocalTime, fmtNumber, time2Timestamp, getDate } from "./util.js"
export default {
name: "XpPicker",
data() {
return {
isError: true,
isConfirm: false,
pickerVisible: false,
template: {},
cols: [],
selected: [],
}
},
props: {
startOrEnd: {
type: String,
default: "start",
},
startTime: {
type: String,
default: "",
},
endTime: {
type: String,
default: "",
},
mode: {
type: String,
default: "ymd",
},
animation: {
type: Boolean,
default: true,
},
height: {
type: [Number, String],
default: 35,
},
"action-position": {
type: String,
default: "bottom",
},
"year-range": {
type: Array,
default: () => [2010, 2035],
},
value: {
type: String,
default: null,
},
history: {
type: Boolean,
default: false,
},
},
watch: {
mode() {
this.render()
},
},
computed: {
hasSlot() {
return !!this.$slots["default"]
},
modeArr() {
return this.mode.split("")
},
units() {
const arr = []
for (const k in this.template) {
if (this.mode.indexOf(k) !== -1) arr.push(this.template[k].text)
}
return arr
},
},
created() {
this.render()
},
methods: {
render() {
this.assert() //检查用户配置
this.template = templateFactory(this) //生成所需列 默认模板
this.initCols() //根据模板 初始化列
this.initSelected() //设置默认值
},
assert() {
if ("ymdhis".indexOf(this.mode) === -1) {
throw new Error("render errorillegal 'mode'")
}
if (getLocalTime(this.mode) == undefined) {
throw new Error("render errorthe 'mode' is not found")
}
if (this.value != null) {
const arr = this.value.split(/-|:|\s/)
if (arr.length != this.modeArr.length) {
throw new Error("render errorbecause the 'value' cannot be formatted as 'mode'")
}
}
if (this.yearRange.length !== 2) {
throw new Error("render errorbecause the length of array 'year-rang' must be 2")
}
this.isError = false
},
initCols() {
for (const k of this.mode) {
const range = this.template[k].range
this.fillCol(k, ...range)
}
},
initSelected() {
const v = this.value || getLocalTime(this.mode)
if (this.startOrEnd === "start") {
this.setSelected(this.startTime)
} else {
this.setSelected(this.endTime)
}
},
fillCol(k, s, e) {
const index = this.mode.indexOf(k)
let arr = []
for (let i = s; i <= e; i++) arr.push(fmtNumber(i))
this.$set(this.cols, index, arr)
},
//dt 时间字符串 如 '2020-02-16'
setSelected(dt) {
const arr = dt.split(/-|:|\s/)
const a = this.cols
for (let i = 0; i < a.length; i++) this.$set(this.selected, i, a[i].indexOf(arr[i]))
},
resolveCurrentDt() {
let str = ""
for (let i = 0; i < this.selected.length; i++) str += this.cols[i][this.selected[i]] + this.units[i]
let dt = str
.replace("年", "-")
.replace("月", "-")
.replace("日", " ")
.replace("时", ":")
.replace("分", ":")
.replace("秒", "")
if (!this.mode.endsWith("s")) dt = dt.substring(0, dt.length - 1)
dt = dt.split("undefined").join("00")
return dt
},
show() {
if (this.history) {
if (!this.isConfirm) this.initSelected()
} else this.initSelected()
this.pickerVisible = true
},
_confirm() {
if (!this.isError) this.$emit("confirm", this._getResult())
if (!this.isConfirm) this.isConfirm = true
this.pickerVisible = false
},
_getResult() {
const detail = {
value: this.resolveCurrentDt(),
}
const tp = time2Timestamp(detail.value)
if (!isNaN(tp)) detail.timestamp = tp
return detail
},
_cancel() {
this.$emit("cancel")
this.pickerVisible = false
},
_change(e) {
let col
const newValue = e.detail.value
for (let i = 0; i < newValue.length; i++) {
if (newValue[i] !== this.selected[i]) {
col = this.modeArr[i]
break
}
}
this.selected = newValue
const index = this.mode.indexOf("d")
if (index !== -1 && (col === "y" || col === "m")) {
const currentDt = this.resolveCurrentDt()
this.fillCol("d", 1, getDate(currentDt))
}
},
},
}
</script>
<style scoped lang="scss">
.xp-picker {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 999;
font-size: 30rpx;
}
.xp-picker-container {
position: fixed;
bottom: 0;
transform: translateY(100%);
z-index: 999;
width: 100%;
background-color: #fff;
visibility: hidden;
}
.xp-picker-container--show {
transform: translateY(0);
visibility: visible;
}
.xp-picker-mask {
z-index: 998;
width: 100%;
height: 100%;
background-color: rgb(0, 0, 0);
}
.xp-picker-animation {
transition: all 0.25s;
}
.xp-picker-error {
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #ff0000;
}
.xp-picker-action {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
height: 90rpx;
padding: 0 28rpx;
box-sizing: border-box;
position: relative;
font-size: 34rpx;
border-bottom: 0.5px solid #e5e5e5;
}
.xp-picker-btns {
width: 100%;
display: flex;
justify-content: space-around;
align-items: center;
height: 160rpx;
padding: 0 20rpx;
box-sizing: border-box;
position: relative;
}
.xp-button {
height: 45px;
}
.xp-button--cancel {
width: 115px;
border-radius: 5px;
background: #fff;
border: 0.5px solid #c5c7cc;
}
.xp-button--confirm {
width: 180px;
height: 45px;
border-radius: 5px;
background: #3981ff;
border: 1.5px solid #3981ff;
color: #fff;
}
.xp-button--confirm,
.xp-button--cancel {
display: flex;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
}
.xp-picker-column {
text-align: center;
border: none;
font-size: 34rpx;
}
.xp-picker-list-item {
display: flex;
justify-content: center;
align-items: center;
height: 40px;
}
</style>