源文件

This commit is contained in:
gyq
2024-05-23 14:39:33 +08:00
commit a1128dd791
2997 changed files with 500069 additions and 0 deletions

View File

@@ -0,0 +1,105 @@
const isLeapYear = y => y % 4 == 0 && y % 100 != 0 || y % 100 == 0 && y % 400 == 0
const variables = {
y: {
text: "年",
range: [null, null]
},
m: {
text: "月",
range: [1, 12]
},
d: {
text: "日",
range: [1, 31]
},
h: {
text: "时",
range: [0, 23]
},
i: {
text: "分",
range: [0, 59]
},
s: {
text: "秒",
range: [0, 59]
}
}
export function templateFactory({
mode,
value,
yearRange
}) {
const [start, end] = yearRange
let ret = {}
for (const key of mode) {
ret[key] = variables[key]
}
if (mode.indexOf("y") !== -1) ret['y'].range = [start || 2016, end || new Date().getFullYear()]
if (mode.indexOf("d") !== -1) {
const date = getDate(value || getLocalTime(mode))
ret['d'].range = [1, date]
}
return ret
}
export function getDate(dt) {
const s = dt.substring(0, dt.lastIndexOf("-"))
let year, month
const d = new Date()
switch (s.length) {
case 0:
year = d.getFullYear()
month = d.getMonth() + 1
break;
case 2:
year = d.getFullYear()
month = parseInt(s)
break;
default:
const [y, m] = s.split("-")
year = parseInt(y)
month = parseInt(m)
break;
}
const days = [31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
return days[month - 1]
}
export function getLocalTime(fmt) {
if (!fmt) return null
const da = new Date()
const y = fmtNumber(da.getFullYear()),
m = fmtNumber(da.getMonth() + 1),
d = fmtNumber(da.getDate()),
h = fmtNumber(da.getHours()),
i = fmtNumber(da.getMinutes()),
s = fmtNumber(da.getSeconds())
const types = {
'y': `${y}`,
'm': `${m}`,
'd': `${d}`,
'h': `${h}`,
'i': `${i}`,
's': `${s}`,
'ym': `${y}-${m}`,
'md': `${m}-${d}`,
'hi': `${h}:${i}`,
'is': `${i}:${s}`,
'ymd': `${y}-${m}-${d}`,
'his': `${h}:${i}:${s}`,
'mdh': `${m}-${d} ${h}`,
'ymdh': `${y}-${m}-${d} ${h}`,
'mdhi': `${m}-${d} ${h}:${i}`,
'mdhis': `${m}-${d} ${h}:${m}:${s}`,
'yd':`${y}-${d}`,
'ymdhi': `${y}-${m}-${d} ${h}:${i}`,
'ymdhis': `${y}-${m}-${d} ${h}:${i}:${s}`,
}
return types[fmt]
}
export function fmtNumber(n) {
// return n.toString().padStart(2,"0")
return n > 9 ? n + "" : "0" + n
}
export function time2Timestamp(timer) {
return new Date(timer).getTime()
}

View File

@@ -0,0 +1,345 @@
<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>