Browse Source
Merge branch 'devlop' of http://git.qniao.cn/dengxiongfei/paper-shopkeeper-app into devlop
devlop
Merge branch 'devlop' of http://git.qniao.cn/dengxiongfei/paper-shopkeeper-app into devlop
devlop
29 changed files with 5959 additions and 81 deletions
Split View
Diff Options
-
48apis/add-paper.js
-
76pages/add-paper/index.vue
-
66pages/mall/index.vue
-
41pages/message/index.vue
-
175pages/paper-detail/index.vue
-
85uni_modules/uni-datetime-picker/changelog.md
-
185uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar-item.vue
-
898uni_modules/uni-datetime-picker/components/uni-datetime-picker/calendar.vue
-
19uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/en.json
-
8uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/index.js
-
19uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hans.json
-
19uni_modules/uni-datetime-picker/components/uni-datetime-picker/i18n/zh-Hant.json
-
45uni_modules/uni-datetime-picker/components/uni-datetime-picker/keypress.js
-
927uni_modules/uni-datetime-picker/components/uni-datetime-picker/time-picker.vue
-
981uni_modules/uni-datetime-picker/components/uni-datetime-picker/uni-datetime-picker.vue
-
410uni_modules/uni-datetime-picker/components/uni-datetime-picker/util.js
-
90uni_modules/uni-datetime-picker/package.json
-
21uni_modules/uni-datetime-picker/readme.md
-
21uni_modules/uni-table/changelog.md
-
455uni_modules/uni-table/components/uni-table/uni-table.vue
-
29uni_modules/uni-table/components/uni-tbody/uni-tbody.vue
-
90uni_modules/uni-table/components/uni-td/uni-td.vue
-
503uni_modules/uni-table/components/uni-th/filter-dropdown.vue
-
251uni_modules/uni-table/components/uni-th/uni-th.vue
-
129uni_modules/uni-table/components/uni-thead/uni-thead.vue
-
179uni_modules/uni-table/components/uni-tr/table-checkbox.vue
-
171uni_modules/uni-table/components/uni-tr/uni-tr.vue
-
86uni_modules/uni-table/package.json
-
13uni_modules/uni-table/readme.md
@ -1,9 +1,44 @@ |
|||
<template> |
|||
<view>消息中心</view> |
|||
<view class="message-info"> |
|||
<view> |
|||
<uni-nav-bar left-icon="back" @clickLeft="back" statusBar fixed title=""> |
|||
<view class="message-info-title">消息通知</view> |
|||
<view slot="left"></view> |
|||
<view slot="right" class="message-info-right-title">一键全读</view> |
|||
</uni-nav-bar> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default {} |
|||
import { back, go2 } from '@/utils/hook.js' |
|||
export default { |
|||
data() { |
|||
return { |
|||
} |
|||
}, |
|||
methods:{ |
|||
back |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style></style> |
|||
<style lang="scss"> |
|||
.message-info{ |
|||
.message-info-title { |
|||
width: 100%; |
|||
font-size: 36rpx; |
|||
color: #000000; |
|||
letter-spacing: 0; |
|||
text-align: center; |
|||
font-weight: 500; |
|||
} |
|||
.message-info-right-title{ |
|||
font-size: 28rpx; |
|||
color: #007AFF; |
|||
text-align: right; |
|||
line-height: 40rpx; |
|||
font-weight: 500; |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,85 @@ |
|||
## 2.2.2(2021-12-10) |
|||
- 修复 clear-icon 属性在小程序平台不生效的 bug |
|||
## 2.2.1(2021-12-10) |
|||
- 修复 日期范围选在小程序平台,必须多点击一次才能取消选中状态的 bug |
|||
## 2.2.0(2021-11-19) |
|||
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) |
|||
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-datetime-picker](https://uniapp.dcloud.io/component/uniui/uni-datetime-picker) |
|||
## 2.1.5(2021-11-09) |
|||
- 新增 提供组件设计资源,组件样式调整 |
|||
## 2.1.4(2021-09-10) |
|||
- 修复 hide-second 在移动端的 bug |
|||
- 修复 单选赋默认值时,赋值日期未高亮的 bug |
|||
- 修复 赋默认值时,移动端未正确显示时间的 bug |
|||
## 2.1.3(2021-09-09) |
|||
- 新增 hide-second 属性,支持只使用时分,隐藏秒 |
|||
## 2.1.2(2021-09-03) |
|||
- 优化 取消选中时(范围选)直接开始下一次选择, 避免多点一次 |
|||
- 优化 移动端支持清除按钮,同时支持通过 ref 调用组件的 clear 方法 |
|||
- 优化 调整字号大小,美化日历界面 |
|||
- 修复 因国际化导致的 placeholder 失效的 bug |
|||
## 2.1.1(2021-08-24) |
|||
- 新增 支持国际化 |
|||
- 优化 范围选择器在 pc 端过宽的问题 |
|||
## 2.1.0(2021-08-09) |
|||
- 新增 适配 vue3 |
|||
## 2.0.19(2021-08-09) |
|||
- 新增 支持作为 uni-forms 子组件相关功能 |
|||
- 修复 在 uni-forms 中使用时,选择时间报 NAN 错误的 bug |
|||
## 2.0.18(2021-08-05) |
|||
- 修复 type 属性动态赋值无效的 bug |
|||
- 修复 ‘确认’按钮被 tabbar 遮盖 bug |
|||
- 修复 组件未赋值时范围选左、右日历相同的 bug |
|||
## 2.0.17(2021-08-04) |
|||
- 修复 范围选未正确显示当前值的 bug |
|||
- 修复 h5 平台(移动端)报错 'cale' of undefined 的 bug |
|||
## 2.0.16(2021-07-21) |
|||
- 新增 return-type 属性支持返回 date 日期对象 |
|||
## 2.0.15(2021-07-14) |
|||
- 修复 单选日期类型,初始赋值后不在当前日历的 bug |
|||
- 新增 clearIcon 属性,显示框的清空按钮可配置显示隐藏(仅 pc 有效) |
|||
- 优化 移动端移除显示框的清空按钮,无实际用途 |
|||
## 2.0.14(2021-07-14) |
|||
- 修复 组件赋值为空,界面未更新的 bug |
|||
- 修复 start 和 end 不能动态赋值的 bug |
|||
- 修复 范围选类型,用户选择后再次选择右侧日历(结束日期)显示不正确的 bug |
|||
## 2.0.13(2021-07-08) |
|||
- 修复 范围选择不能动态赋值的 bug |
|||
## 2.0.12(2021-07-08) |
|||
- 修复 范围选择的初始时间在一个月内时,造成无法选择的bug |
|||
## 2.0.11(2021-07-08) |
|||
- 优化 弹出层在超出视窗边缘定位不准确的问题 |
|||
## 2.0.10(2021-07-08) |
|||
- 修复 范围起始点样式的背景色与今日样式的字体前景色融合,导致日期字体看不清的 bug |
|||
- 优化 弹出层在超出视窗边缘被遮盖的问题 |
|||
## 2.0.9(2021-07-07) |
|||
- 新增 maskClick 事件 |
|||
- 修复 特殊情况日历 rpx 布局错误的 bug,rpx -> px |
|||
- 修复 范围选择时清空返回值不合理的bug,['', ''] -> [] |
|||
## 2.0.8(2021-07-07) |
|||
- 新增 日期时间显示框支持插槽 |
|||
## 2.0.7(2021-07-01) |
|||
- 优化 添加 uni-icons 依赖 |
|||
## 2.0.6(2021-05-22) |
|||
- 修复 图标在小程序上不显示的 bug |
|||
- 优化 重命名引用组件,避免潜在组件命名冲突 |
|||
## 2.0.5(2021-05-20) |
|||
- 优化 代码目录扁平化 |
|||
## 2.0.4(2021-05-12) |
|||
- 新增 组件示例地址 |
|||
## 2.0.3(2021-05-10) |
|||
- 修复 ios 下不识别 '-' 日期格式的 bug |
|||
- 优化 pc 下弹出层添加边框和阴影 |
|||
## 2.0.2(2021-05-08) |
|||
- 修复 在 admin 中获取弹出层定位错误的bug |
|||
## 2.0.1(2021-05-08) |
|||
- 修复 type 属性向下兼容,默认值从 date 变更为 datetime |
|||
## 2.0.0(2021-04-30) |
|||
- 支持日历形式的日期+时间的范围选择 |
|||
> 注意:此版本不向后兼容,不再支持单独时间选择(type=time)及相关的 hide-second 属性(时间选可使用内置组件 picker) |
|||
## 1.0.6(2021-03-18) |
|||
- 新增 hide-second 属性,时间支持仅选择时、分 |
|||
- 修复 选择跟显示的日期不一样的 bug |
|||
- 修复 chang事件触发2次的 bug |
|||
- 修复 分、秒 end 范围错误的 bug |
|||
- 优化 更好的 nvue 适配 |
|||
@ -0,0 +1,185 @@ |
|||
<template> |
|||
<view class="uni-calendar-item__weeks-box" :class="{ |
|||
'uni-calendar-item--disable':weeks.disable, |
|||
'uni-calendar-item--before-checked-x':weeks.beforeMultiple, |
|||
'uni-calendar-item--multiple': weeks.multiple, |
|||
'uni-calendar-item--after-checked-x':weeks.afterMultiple, |
|||
}" @click="choiceDate(weeks)" @mouseenter="handleMousemove(weeks)"> |
|||
<view class="uni-calendar-item__weeks-box-item" :class="{ |
|||
'uni-calendar-item--checked':calendar.fullDate === weeks.fullDate && (calendar.userChecked || !checkHover), |
|||
'uni-calendar-item--checked-range-text': checkHover, |
|||
'uni-calendar-item--before-checked':weeks.beforeMultiple, |
|||
'uni-calendar-item--multiple': weeks.multiple, |
|||
'uni-calendar-item--after-checked':weeks.afterMultiple, |
|||
'uni-calendar-item--disable':weeks.disable, |
|||
}"> |
|||
<text v-if="selected&&weeks.extraInfo" class="uni-calendar-item__weeks-box-circle"></text> |
|||
<text class="uni-calendar-item__weeks-box-text uni-calendar-item__weeks-box-text-disable uni-calendar-item--checked-text">{{weeks.date}}</text> |
|||
</view> |
|||
<view :class="{'uni-calendar-item--isDay': weeks.isDay}"></view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
props: { |
|||
weeks: { |
|||
type: Object, |
|||
default () { |
|||
return {} |
|||
} |
|||
}, |
|||
calendar: { |
|||
type: Object, |
|||
default: () => { |
|||
return {} |
|||
} |
|||
}, |
|||
selected: { |
|||
type: Array, |
|||
default: () => { |
|||
return [] |
|||
} |
|||
}, |
|||
lunar: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
checkHover: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
methods: { |
|||
choiceDate(weeks) { |
|||
this.$emit('change', weeks) |
|||
}, |
|||
handleMousemove(weeks) { |
|||
this.$emit('handleMouse', weeks) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.uni-calendar-item__weeks-box { |
|||
flex: 1; |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
align-items: center; |
|||
margin: 1px 0; |
|||
position: relative; |
|||
} |
|||
|
|||
.uni-calendar-item__weeks-box-text { |
|||
font-size: 14px; |
|||
// font-family: Lato-Bold, Lato; |
|||
font-weight: bold; |
|||
color: #455997; |
|||
} |
|||
|
|||
.uni-calendar-item__weeks-lunar-text { |
|||
font-size: 12px; |
|||
color: #333; |
|||
} |
|||
|
|||
.uni-calendar-item__weeks-box-item { |
|||
position: relative; |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
align-items: center; |
|||
width: 40px; |
|||
height: 40px; |
|||
/* #ifdef H5 */ |
|||
cursor: pointer; |
|||
/* #endif */ |
|||
} |
|||
|
|||
|
|||
.uni-calendar-item__weeks-box-circle { |
|||
position: absolute; |
|||
top: 5px; |
|||
right: 5px; |
|||
width: 8px; |
|||
height: 8px; |
|||
border-radius: 8px; |
|||
background-color: #dd524d; |
|||
|
|||
} |
|||
|
|||
.uni-calendar-item__weeks-box .uni-calendar-item--disable { |
|||
// background-color: rgba(249, 249, 249, $uni-opacity-disabled); |
|||
cursor: default; |
|||
} |
|||
|
|||
.uni-calendar-item--disable .uni-calendar-item__weeks-box-text-disable { |
|||
color: #D1D1D1; |
|||
} |
|||
|
|||
.uni-calendar-item--isDay { |
|||
position: absolute; |
|||
top: 10px; |
|||
right: 17%; |
|||
background-color: #dd524d; |
|||
width:6px; |
|||
height: 6px; |
|||
border-radius: 50%; |
|||
} |
|||
|
|||
.uni-calendar-item--extra { |
|||
color: #dd524d; |
|||
opacity: 0.8; |
|||
} |
|||
|
|||
.uni-calendar-item__weeks-box .uni-calendar-item--checked { |
|||
background-color: #007aff; |
|||
border-radius: 50%; |
|||
box-sizing: border-box; |
|||
border: 3px solid #fff; |
|||
} |
|||
|
|||
.uni-calendar-item--checked .uni-calendar-item--checked-text { |
|||
color: #fff; |
|||
} |
|||
|
|||
.uni-calendar-item--multiple .uni-calendar-item--checked-range-text { |
|||
color: #333; |
|||
} |
|||
|
|||
.uni-calendar-item--multiple { |
|||
background-color: #F6F7FC; |
|||
// color: #fff; |
|||
} |
|||
|
|||
.uni-calendar-item--multiple .uni-calendar-item--before-checked, |
|||
.uni-calendar-item--multiple .uni-calendar-item--after-checked { |
|||
background-color: #409eff; |
|||
border-radius: 50%; |
|||
box-sizing: border-box; |
|||
border: 3px solid #F6F7FC; |
|||
} |
|||
|
|||
.uni-calendar-item--before-checked .uni-calendar-item--checked-text, |
|||
.uni-calendar-item--after-checked .uni-calendar-item--checked-text { |
|||
color: #fff; |
|||
} |
|||
|
|||
.uni-calendar-item--before-checked-x { |
|||
border-top-left-radius: 50px; |
|||
border-bottom-left-radius: 50px; |
|||
box-sizing: border-box; |
|||
background-color: #F6F7FC; |
|||
} |
|||
|
|||
.uni-calendar-item--after-checked-x { |
|||
border-top-right-radius: 50px; |
|||
border-bottom-right-radius: 50px; |
|||
background-color: #F6F7FC; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,898 @@ |
|||
<template> |
|||
<view class="uni-calendar" @mouseleave="leaveCale"> |
|||
<view v-if="!insert&&show" class="uni-calendar__mask" :class="{'uni-calendar--mask-show':aniMaskShow}" |
|||
@click="clean"></view> |
|||
<view v-if="insert || show" class="uni-calendar__content" |
|||
:class="{'uni-calendar--fixed':!insert,'uni-calendar--ani-show':aniMaskShow, 'uni-calendar__content-mobile': aniMaskShow}"> |
|||
<view class="uni-calendar__header" :class="{'uni-calendar__header-mobile' :!insert}"> |
|||
<view v-if="left" class="uni-calendar__header-btn-box" @click.stop="pre"> |
|||
<view class="uni-calendar__header-btn uni-calendar--left"></view> |
|||
</view> |
|||
<picker mode="date" :value="date" fields="month" @change="bindDateChange"> |
|||
<text |
|||
class="uni-calendar__header-text">{{ (nowDate.year||'') + ' 年 ' + ( nowDate.month||'') +' 月'}}</text> |
|||
</picker> |
|||
<view v-if="right" class="uni-calendar__header-btn-box" @click.stop="next"> |
|||
<view class="uni-calendar__header-btn uni-calendar--right"></view> |
|||
</view> |
|||
<view v-if="!insert" class="dialog-close" @click="clean"> |
|||
<view class="dialog-close-plus" data-id="close"></view> |
|||
<view class="dialog-close-plus dialog-close-rotate" data-id="close"></view> |
|||
</view> |
|||
|
|||
<!-- <text class="uni-calendar__backtoday" @click="backtoday">回到今天</text> --> |
|||
</view> |
|||
<view class="uni-calendar__box"> |
|||
<view v-if="showMonth" class="uni-calendar__box-bg"> |
|||
<text class="uni-calendar__box-bg-text">{{nowDate.month}}</text> |
|||
</view> |
|||
<view class="uni-calendar__weeks" style="padding-bottom: 7px;"> |
|||
<view class="uni-calendar__weeks-day"> |
|||
<text class="uni-calendar__weeks-day-text">{{SUNText}}</text> |
|||
</view> |
|||
<view class="uni-calendar__weeks-day"> |
|||
<text class="uni-calendar__weeks-day-text">{{monText}}</text> |
|||
</view> |
|||
<view class="uni-calendar__weeks-day"> |
|||
<text class="uni-calendar__weeks-day-text">{{TUEText}}</text> |
|||
</view> |
|||
<view class="uni-calendar__weeks-day"> |
|||
<text class="uni-calendar__weeks-day-text">{{WEDText}}</text> |
|||
</view> |
|||
<view class="uni-calendar__weeks-day"> |
|||
<text class="uni-calendar__weeks-day-text">{{THUText}}</text> |
|||
</view> |
|||
<view class="uni-calendar__weeks-day"> |
|||
<text class="uni-calendar__weeks-day-text">{{FRIText}}</text> |
|||
</view> |
|||
<view class="uni-calendar__weeks-day"> |
|||
<text class="uni-calendar__weeks-day-text">{{SATText}}</text> |
|||
</view> |
|||
</view> |
|||
<view class="uni-calendar__weeks" v-for="(item,weekIndex) in weeks" :key="weekIndex"> |
|||
<view class="uni-calendar__weeks-item" v-for="(weeks,weeksIndex) in item" :key="weeksIndex"> |
|||
<calendar-item class="uni-calendar-item--hook" :weeks="weeks" :calendar="calendar" |
|||
:selected="selected" :lunar="lunar" :checkHover="range" @change="choiceDate" |
|||
@handleMouse="handleMouse"> |
|||
</calendar-item> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<view v-if="!insert && !range && typeHasTime" class="uni-date-changed uni-calendar--fixed-top" |
|||
style="padding: 0 80px;"> |
|||
<view class="uni-date-changed--time-date">{{tempSingleDate ? tempSingleDate : selectDateText}}</view> |
|||
<time-picker type="time" :start="reactStartTime" :end="reactEndTime" v-model="time" |
|||
:disabled="!tempSingleDate" :border="false" :hide-second="hideSecond" class="time-picker-style"> |
|||
</time-picker> |
|||
</view> |
|||
|
|||
<view v-if="!insert && range && typeHasTime" class="uni-date-changed uni-calendar--fixed-top"> |
|||
<view class="uni-date-changed--time-start"> |
|||
<view class="uni-date-changed--time-date">{{tempRange.before ? tempRange.before : startDateText}} |
|||
</view> |
|||
<time-picker type="time" :start="reactStartTime" v-model="timeRange.startTime" :border="false" |
|||
:hide-second="hideSecond" :disabled="!tempRange.before" class="time-picker-style"> |
|||
</time-picker> |
|||
</view> |
|||
<uni-icons type="arrowthinright" color="#999" style="line-height: 50px;"></uni-icons> |
|||
<view class="uni-date-changed--time-end"> |
|||
<view class="uni-date-changed--time-date">{{tempRange.after ? tempRange.after : endDateText}}</view> |
|||
<time-picker type="time" :end="reactEndTime" v-model="timeRange.endTime" :border="false" |
|||
:hide-second="hideSecond" :disabled="!tempRange.after" class="time-picker-style"> |
|||
</time-picker> |
|||
</view> |
|||
</view> |
|||
<view v-if="!insert" class="uni-date-changed uni-date-btn--ok"> |
|||
<!-- <view class="uni-calendar__header-btn-box"> |
|||
<text class="uni-calendar__button-text uni-calendar--fixed-width">{{okText}}</text> |
|||
</view> --> |
|||
<view class="uni-datetime-picker--btn" @click="confirm">确认</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import Calendar from './util.js'; |
|||
import calendarItem from './calendar-item.vue' |
|||
import timePicker from './time-picker.vue' |
|||
import { |
|||
initVueI18n |
|||
} from '@dcloudio/uni-i18n' |
|||
import messages from './i18n/index.js' |
|||
const { |
|||
t |
|||
} = initVueI18n(messages) |
|||
/** |
|||
* Calendar 日历 |
|||
* @description 日历组件可以查看日期,选择任意范围内的日期,打点操作。常用场景如:酒店日期预订、火车机票选择购买日期、上下班打卡等 |
|||
* @tutorial https://ext.dcloud.net.cn/plugin?id=56 |
|||
* @property {String} date 自定义当前时间,默认为今天 |
|||
* @property {Boolean} lunar 显示农历 |
|||
* @property {String} startDate 日期选择范围-开始日期 |
|||
* @property {String} endDate 日期选择范围-结束日期 |
|||
* @property {Boolean} range 范围选择 |
|||
* @property {Boolean} insert = [true|false] 插入模式,默认为false |
|||
* @value true 弹窗模式 |
|||
* @value false 插入模式 |
|||
* @property {Boolean} clearDate = [true|false] 弹窗模式是否清空上次选择内容 |
|||
* @property {Array} selected 打点,期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}] |
|||
* @property {Boolean} showMonth 是否选择月份为背景 |
|||
* @event {Function} change 日期改变,`insert :ture` 时生效 |
|||
* @event {Function} confirm 确认选择`insert :false` 时生效 |
|||
* @event {Function} monthSwitch 切换月份时触发 |
|||
* @example <uni-calendar :insert="true":lunar="true" :start-date="'2019-3-2'":end-date="'2019-5-20'"@change="change" /> |
|||
*/ |
|||
export default { |
|||
components: { |
|||
calendarItem, |
|||
timePicker |
|||
}, |
|||
props: { |
|||
date: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
defTime: { |
|||
type: [String, Object], |
|||
default: '' |
|||
}, |
|||
selectableTimes: { |
|||
type: [Object], |
|||
default () { |
|||
return {} |
|||
} |
|||
}, |
|||
selected: { |
|||
type: Array, |
|||
default () { |
|||
return [] |
|||
} |
|||
}, |
|||
lunar: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
startDate: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
endDate: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
range: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
typeHasTime: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
insert: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
showMonth: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
clearDate: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
left: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
right: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
checkHover: { |
|||
type: Boolean, |
|||
default: true |
|||
}, |
|||
hideSecond: { |
|||
type: [Boolean], |
|||
default: false |
|||
}, |
|||
pleStatus: { |
|||
type: Object, |
|||
default () { |
|||
return { |
|||
before: '', |
|||
after: '', |
|||
data: [], |
|||
fulldate: '' |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
show: false, |
|||
weeks: [], |
|||
calendar: {}, |
|||
nowDate: '', |
|||
aniMaskShow: false, |
|||
firstEnter: true, |
|||
time: '', |
|||
timeRange: { |
|||
startTime: '', |
|||
endTime: '' |
|||
}, |
|||
tempSingleDate: '', |
|||
tempRange: { |
|||
before: '', |
|||
after: '' |
|||
} |
|||
} |
|||
}, |
|||
watch: { |
|||
date: { |
|||
immediate: true, |
|||
handler(newVal, oldVal) { |
|||
if (!this.range) { |
|||
this.tempSingleDate = newVal |
|||
setTimeout(() => { |
|||
this.init(newVal) |
|||
}, 100) |
|||
} |
|||
} |
|||
}, |
|||
defTime: { |
|||
immediate: true, |
|||
handler(newVal, oldVal) { |
|||
if (!this.range) { |
|||
this.time = newVal |
|||
} else { |
|||
// console.log('-----', newVal); |
|||
this.timeRange.startTime = newVal.start |
|||
this.timeRange.endTime = newVal.end |
|||
} |
|||
} |
|||
}, |
|||
startDate(val) { |
|||
this.cale.resetSatrtDate(val) |
|||
this.cale.setDate(this.nowDate.fullDate) |
|||
this.weeks = this.cale.weeks |
|||
}, |
|||
endDate(val) { |
|||
this.cale.resetEndDate(val) |
|||
this.cale.setDate(this.nowDate.fullDate) |
|||
this.weeks = this.cale.weeks |
|||
}, |
|||
selected(newVal) { |
|||
this.cale.setSelectInfo(this.nowDate.fullDate, newVal) |
|||
this.weeks = this.cale.weeks |
|||
}, |
|||
pleStatus: { |
|||
immediate: true, |
|||
handler(newVal, oldVal) { |
|||
const { |
|||
before, |
|||
after, |
|||
fulldate, |
|||
which |
|||
} = newVal |
|||
this.tempRange.before = before |
|||
this.tempRange.after = after |
|||
setTimeout(() => { |
|||
if (fulldate) { |
|||
this.cale.setHoverMultiple(fulldate) |
|||
if (before && after) { |
|||
this.cale.lastHover = true |
|||
if (this.rangeWithinMonth(after, before)) return |
|||
this.setDate(before) |
|||
} else { |
|||
this.cale.setMultiple(fulldate) |
|||
this.setDate(this.nowDate.fullDate) |
|||
this.calendar.fullDate = '' |
|||
this.cale.lastHover = false |
|||
} |
|||
} else { |
|||
this.cale.setDefaultMultiple(before, after) |
|||
if (which === 'left') { |
|||
this.setDate(before) |
|||
this.weeks = this.cale.weeks |
|||
} else { |
|||
this.setDate(after) |
|||
this.weeks = this.cale.weeks |
|||
} |
|||
this.cale.lastHover = true |
|||
} |
|||
}, 16) |
|||
} |
|||
} |
|||
}, |
|||
computed: { |
|||
reactStartTime() { |
|||
const activeDate = this.range ? this.tempRange.before : this.calendar.fullDate |
|||
const res = activeDate === this.startDate ? this.selectableTimes.start : '' |
|||
return res |
|||
}, |
|||
reactEndTime() { |
|||
const activeDate = this.range ? this.tempRange.after : this.calendar.fullDate |
|||
const res = activeDate === this.endDate ? this.selectableTimes.end : '' |
|||
return res |
|||
}, |
|||
/** |
|||
* for i18n |
|||
*/ |
|||
selectDateText() { |
|||
return t("uni-datetime-picker.selectDate") |
|||
}, |
|||
startDateText() { |
|||
return this.startPlaceholder || t("uni-datetime-picker.startDate") |
|||
}, |
|||
endDateText() { |
|||
return this.endPlaceholder || t("uni-datetime-picker.endDate") |
|||
}, |
|||
okText() { |
|||
return t("uni-datetime-picker.ok") |
|||
}, |
|||
monText() { |
|||
return t("uni-calender.MON") |
|||
}, |
|||
TUEText() { |
|||
return t("uni-calender.TUE") |
|||
}, |
|||
WEDText() { |
|||
return t("uni-calender.WED") |
|||
}, |
|||
THUText() { |
|||
return t("uni-calender.THU") |
|||
}, |
|||
FRIText() { |
|||
return t("uni-calender.FRI") |
|||
}, |
|||
SATText() { |
|||
return t("uni-calender.SAT") |
|||
}, |
|||
SUNText() { |
|||
return t("uni-calender.SUN") |
|||
}, |
|||
}, |
|||
created() { |
|||
// 获取日历方法实例 |
|||
this.cale = new Calendar({ |
|||
// date: new Date(), |
|||
selected: this.selected, |
|||
startDate: this.startDate, |
|||
endDate: this.endDate, |
|||
range: this.range, |
|||
// multipleStatus: this.pleStatus |
|||
}) |
|||
// 选中某一天 |
|||
// this.cale.setDate(this.date) |
|||
this.init(this.date) |
|||
// this.setDay |
|||
}, |
|||
methods: { |
|||
leaveCale() { |
|||
this.firstEnter = true |
|||
}, |
|||
handleMouse(weeks) { |
|||
if (weeks.disable) return |
|||
if (this.cale.lastHover) return |
|||
let { |
|||
before, |
|||
after |
|||
} = this.cale.multipleStatus |
|||
if (!before) return |
|||
this.calendar = weeks |
|||
// 设置范围选 |
|||
this.cale.setHoverMultiple(this.calendar.fullDate) |
|||
this.weeks = this.cale.weeks |
|||
// hover时,进入一个日历,更新另一个 |
|||
if (this.firstEnter) { |
|||
this.$emit('firstEnterCale', this.cale.multipleStatus) |
|||
this.firstEnter = false |
|||
} |
|||
}, |
|||
rangeWithinMonth(A, B) { |
|||
const [yearA, monthA] = A.split('-') |
|||
const [yearB, monthB] = B.split('-') |
|||
return yearA === yearB && monthA === monthB |
|||
}, |
|||
|
|||
// 取消穿透 |
|||
clean() { |
|||
this.close() |
|||
}, |
|||
|
|||
clearCalender() { |
|||
if (this.range) { |
|||
this.timeRange.startTime = '' |
|||
this.timeRange.endTime = '' |
|||
this.tempRange.before = '' |
|||
this.tempRange.after = '' |
|||
this.cale.multipleStatus.before = '' |
|||
this.cale.multipleStatus.after = '' |
|||
this.cale.multipleStatus.data = [] |
|||
this.cale.lastHover = false |
|||
} else { |
|||
this.time = '' |
|||
this.tempSingleDate = '' |
|||
} |
|||
this.calendar.fullDate = '' |
|||
this.setDate() |
|||
}, |
|||
|
|||
bindDateChange(e) { |
|||
const value = e.detail.value + '-1' |
|||
this.init(value) |
|||
}, |
|||
/** |
|||
* 初始化日期显示 |
|||
* @param {Object} date |
|||
*/ |
|||
init(date) { |
|||
this.cale.setDate(date) |
|||
this.weeks = this.cale.weeks |
|||
this.nowDate = this.calendar = this.cale.getInfo(date) |
|||
}, |
|||
// choiceDate(weeks) { |
|||
// if (weeks.disable) return |
|||
// this.calendar = weeks |
|||
// // 设置多选 |
|||
// this.cale.setMultiple(this.calendar.fullDate, true) |
|||
// this.weeks = this.cale.weeks |
|||
// this.tempSingleDate = this.calendar.fullDate |
|||
// this.tempRange.before = this.cale.multipleStatus.before |
|||
// this.tempRange.after = this.cale.multipleStatus.after |
|||
// this.change() |
|||
// }, |
|||
/** |
|||
* 打开日历弹窗 |
|||
*/ |
|||
open() { |
|||
// 弹窗模式并且清理数据 |
|||
if (this.clearDate && !this.insert) { |
|||
this.cale.cleanMultipleStatus() |
|||
// this.cale.setDate(this.date) |
|||
this.init(this.date) |
|||
} |
|||
this.show = true |
|||
this.$nextTick(() => { |
|||
setTimeout(() => { |
|||
this.aniMaskShow = true |
|||
}, 50) |
|||
}) |
|||
}, |
|||
/** |
|||
* 关闭日历弹窗 |
|||
*/ |
|||
close() { |
|||
this.aniMaskShow = false |
|||
this.$nextTick(() => { |
|||
setTimeout(() => { |
|||
this.show = false |
|||
this.$emit('close') |
|||
}, 300) |
|||
}) |
|||
}, |
|||
/** |
|||
* 确认按钮 |
|||
*/ |
|||
confirm() { |
|||
this.setEmit('confirm') |
|||
this.close() |
|||
}, |
|||
/** |
|||
* 变化触发 |
|||
*/ |
|||
change() { |
|||
if (!this.insert) return |
|||
this.setEmit('change') |
|||
}, |
|||
/** |
|||
* 选择月份触发 |
|||
*/ |
|||
monthSwitch() { |
|||
let { |
|||
year, |
|||
month |
|||
} = this.nowDate |
|||
this.$emit('monthSwitch', { |
|||
year, |
|||
month: Number(month) |
|||
}) |
|||
}, |
|||
/** |
|||
* 派发事件 |
|||
* @param {Object} name |
|||
*/ |
|||
setEmit(name) { |
|||
let { |
|||
year, |
|||
month, |
|||
date, |
|||
fullDate, |
|||
lunar, |
|||
extraInfo |
|||
} = this.calendar |
|||
this.$emit(name, { |
|||
range: this.cale.multipleStatus, |
|||
year, |
|||
month, |
|||
date, |
|||
time: this.time, |
|||
timeRange: this.timeRange, |
|||
fulldate: fullDate, |
|||
lunar, |
|||
extraInfo: extraInfo || {} |
|||
}) |
|||
}, |
|||
/** |
|||
* 选择天触发 |
|||
* @param {Object} weeks |
|||
*/ |
|||
choiceDate(weeks) { |
|||
if (weeks.disable) return |
|||
this.calendar = weeks |
|||
this.calendar.userChecked = true |
|||
// 设置多选 |
|||
this.cale.setMultiple(this.calendar.fullDate, true) |
|||
this.weeks = this.cale.weeks |
|||
this.tempSingleDate = this.calendar.fullDate |
|||
this.tempRange.before = this.cale.multipleStatus.before |
|||
this.tempRange.after = this.cale.multipleStatus.after |
|||
this.change() |
|||
}, |
|||
/** |
|||
* 回到今天 |
|||
*/ |
|||
backtoday() { |
|||
let date = this.cale.getDate(new Date()).fullDate |
|||
// this.cale.setDate(date) |
|||
this.init(date) |
|||
this.change() |
|||
}, |
|||
/** |
|||
* 比较时间大小 |
|||
*/ |
|||
dateCompare(startDate, endDate) { |
|||
// 计算截止时间 |
|||
startDate = new Date(startDate.replace('-', '/').replace('-', '/')) |
|||
// 计算详细项的截止时间 |
|||
endDate = new Date(endDate.replace('-', '/').replace('-', '/')) |
|||
if (startDate <= endDate) { |
|||
return true |
|||
} else { |
|||
return false |
|||
} |
|||
}, |
|||
/** |
|||
* 上个月 |
|||
*/ |
|||
pre() { |
|||
const preDate = this.cale.getDate(this.nowDate.fullDate, -1, 'month').fullDate |
|||
this.setDate(preDate) |
|||
this.monthSwitch() |
|||
|
|||
}, |
|||
/** |
|||
* 下个月 |
|||
*/ |
|||
next() { |
|||
const nextDate = this.cale.getDate(this.nowDate.fullDate, +1, 'month').fullDate |
|||
this.setDate(nextDate) |
|||
this.monthSwitch() |
|||
}, |
|||
/** |
|||
* 设置日期 |
|||
* @param {Object} date |
|||
*/ |
|||
setDate(date) { |
|||
this.cale.setDate(date) |
|||
this.weeks = this.cale.weeks |
|||
this.nowDate = this.cale.getInfo(date) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.uni-calendar { |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.uni-calendar__mask { |
|||
position: fixed; |
|||
bottom: 0; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
background-color: rgba(0, 0, 0, 0.4); |
|||
transition-property: opacity; |
|||
transition-duration: 0.3s; |
|||
opacity: 0; |
|||
/* #ifndef APP-NVUE */ |
|||
z-index: 99; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.uni-calendar--mask-show { |
|||
opacity: 1 |
|||
} |
|||
|
|||
.uni-calendar--fixed { |
|||
position: fixed; |
|||
bottom: calc(var(--window-bottom)); |
|||
left: 0; |
|||
right: 0; |
|||
transition-property: transform; |
|||
transition-duration: 0.3s; |
|||
transform: translateY(460px); |
|||
/* #ifndef APP-NVUE */ |
|||
z-index: 99; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.uni-calendar--ani-show { |
|||
transform: translateY(0); |
|||
} |
|||
|
|||
.uni-calendar__content { |
|||
background-color: #fff; |
|||
} |
|||
|
|||
.uni-calendar__content-mobile { |
|||
border-top-left-radius: 10px; |
|||
border-top-right-radius: 10px; |
|||
box-shadow: 0px 0px 5px 3px rgba(0, 0, 0, 0.1); |
|||
} |
|||
|
|||
.uni-calendar__header { |
|||
position: relative; |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
flex-direction: row; |
|||
justify-content: center; |
|||
align-items: center; |
|||
height: 50px; |
|||
} |
|||
|
|||
.uni-calendar__header-mobile { |
|||
padding: 10px; |
|||
padding-bottom: 0; |
|||
} |
|||
|
|||
.uni-calendar--fixed-top { |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
flex-direction: row; |
|||
justify-content: space-between; |
|||
border-top-color: rgba(0, 0, 0, 0.4); |
|||
border-top-style: solid; |
|||
border-top-width: 1px; |
|||
} |
|||
|
|||
.uni-calendar--fixed-width { |
|||
width: 50px; |
|||
} |
|||
|
|||
.uni-calendar__backtoday { |
|||
position: absolute; |
|||
right: 0; |
|||
top: 25rpx; |
|||
padding: 0 5px; |
|||
padding-left: 10px; |
|||
height: 25px; |
|||
line-height: 25px; |
|||
font-size: 12px; |
|||
border-top-left-radius: 25px; |
|||
border-bottom-left-radius: 25px; |
|||
color: #fff; |
|||
background-color: #f1f1f1; |
|||
} |
|||
|
|||
.uni-calendar__header-text { |
|||
text-align: center; |
|||
width: 100px; |
|||
font-size: 15px; |
|||
color: #666; |
|||
} |
|||
|
|||
.uni-calendar__button-text { |
|||
text-align: center; |
|||
width: 100px; |
|||
font-size: 14px; |
|||
color: #007aff; |
|||
/* #ifndef APP-NVUE */ |
|||
letter-spacing: 3px; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.uni-calendar__header-btn-box { |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
flex-direction: row; |
|||
align-items: center; |
|||
justify-content: center; |
|||
width: 50px; |
|||
height: 50px; |
|||
} |
|||
|
|||
.uni-calendar__header-btn { |
|||
width: 9px; |
|||
height: 9px; |
|||
border-left-color: #808080; |
|||
border-left-style: solid; |
|||
border-left-width: 1px; |
|||
border-top-color: #555555; |
|||
border-top-style: solid; |
|||
border-top-width: 1px; |
|||
} |
|||
|
|||
.uni-calendar--left { |
|||
transform: rotate(-45deg); |
|||
} |
|||
|
|||
.uni-calendar--right { |
|||
transform: rotate(135deg); |
|||
} |
|||
|
|||
|
|||
.uni-calendar__weeks { |
|||
position: relative; |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
flex-direction: row; |
|||
} |
|||
|
|||
.uni-calendar__weeks-item { |
|||
flex: 1; |
|||
} |
|||
|
|||
.uni-calendar__weeks-day { |
|||
flex: 1; |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
align-items: center; |
|||
height: 40px; |
|||
border-bottom-color: #F5F5F5; |
|||
border-bottom-style: solid; |
|||
border-bottom-width: 1px; |
|||
} |
|||
|
|||
.uni-calendar__weeks-day-text { |
|||
font-size: 12px; |
|||
color: #B2B2B2; |
|||
} |
|||
|
|||
.uni-calendar__box { |
|||
position: relative; |
|||
// padding: 0 10px; |
|||
padding-bottom: 7px; |
|||
} |
|||
|
|||
.uni-calendar__box-bg { |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
justify-content: center; |
|||
align-items: center; |
|||
position: absolute; |
|||
top: 0; |
|||
left: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
} |
|||
|
|||
.uni-calendar__box-bg-text { |
|||
font-size: 200px; |
|||
font-weight: bold; |
|||
color: #999; |
|||
opacity: 0.1; |
|||
text-align: center; |
|||
/* #ifndef APP-NVUE */ |
|||
line-height: 1; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.uni-date-changed { |
|||
padding: 0 10px; |
|||
// line-height: 50px; |
|||
text-align: center; |
|||
color: #333; |
|||
border-top-color: #DCDCDC; |
|||
; |
|||
border-top-style: solid; |
|||
border-top-width: 1px; |
|||
flex: 1; |
|||
} |
|||
|
|||
.uni-date-btn--ok { |
|||
padding: 20px 15px; |
|||
} |
|||
|
|||
.uni-date-changed--time-start { |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
align-items: center; |
|||
} |
|||
|
|||
.uni-date-changed--time-end { |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
align-items: center; |
|||
} |
|||
|
|||
.uni-date-changed--time-date { |
|||
color: #999; |
|||
line-height: 50px; |
|||
margin-right: 5px; |
|||
// opacity: 0.6; |
|||
} |
|||
|
|||
.time-picker-style { |
|||
// width: 62px; |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
justify-content: center; |
|||
align-items: center |
|||
} |
|||
|
|||
.mr-10 { |
|||
margin-right: 10px; |
|||
} |
|||
|
|||
.dialog-close { |
|||
position: absolute; |
|||
top: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
flex-direction: row; |
|||
align-items: center; |
|||
padding: 0 25px; |
|||
margin-top: 10px; |
|||
} |
|||
|
|||
.dialog-close-plus { |
|||
width: 16px; |
|||
height: 2px; |
|||
background-color: #737987; |
|||
border-radius: 2px; |
|||
transform: rotate(45deg); |
|||
} |
|||
|
|||
.dialog-close-rotate { |
|||
position: absolute; |
|||
transform: rotate(-45deg); |
|||
} |
|||
|
|||
.uni-datetime-picker--btn { |
|||
border-radius: 100px; |
|||
height: 40px; |
|||
line-height: 40px; |
|||
background-color: #007aff; |
|||
color: #fff; |
|||
font-size: 16px; |
|||
letter-spacing: 5px; |
|||
} |
|||
|
|||
/* #ifndef APP-NVUE */ |
|||
.uni-datetime-picker--btn:active { |
|||
opacity: 0.7; |
|||
} |
|||
/* #endif */ |
|||
</style> |
|||
@ -0,0 +1,19 @@ |
|||
{ |
|||
"uni-datetime-picker.selectDate": "select date", |
|||
"uni-datetime-picker.selectTime": "select time", |
|||
"uni-datetime-picker.selectDateTime": "select datetime", |
|||
"uni-datetime-picker.startDate": "start date", |
|||
"uni-datetime-picker.endDate": "end date", |
|||
"uni-datetime-picker.startTime": "start time", |
|||
"uni-datetime-picker.endTime": "end time", |
|||
"uni-datetime-picker.ok": "ok", |
|||
"uni-datetime-picker.clear": "clear", |
|||
"uni-datetime-picker.cancel": "cancel", |
|||
"uni-calender.MON": "MON", |
|||
"uni-calender.TUE": "TUE", |
|||
"uni-calender.WED": "WED", |
|||
"uni-calender.THU": "THU", |
|||
"uni-calender.FRI": "FRI", |
|||
"uni-calender.SAT": "SAT", |
|||
"uni-calender.SUN": "SUN" |
|||
} |
|||
@ -0,0 +1,8 @@ |
|||
import en from './en.json' |
|||
import zhHans from './zh-Hans.json' |
|||
import zhHant from './zh-Hant.json' |
|||
export default { |
|||
en, |
|||
'zh-Hans': zhHans, |
|||
'zh-Hant': zhHant |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
{ |
|||
"uni-datetime-picker.selectDate": "选择日期", |
|||
"uni-datetime-picker.selectTime": "选择时间", |
|||
"uni-datetime-picker.selectDateTime": "选择日期时间", |
|||
"uni-datetime-picker.startDate": "开始日期", |
|||
"uni-datetime-picker.endDate": "结束日期", |
|||
"uni-datetime-picker.startTime": "开始时间", |
|||
"uni-datetime-picker.endTime": "结束时间", |
|||
"uni-datetime-picker.ok": "确定", |
|||
"uni-datetime-picker.clear": "清除", |
|||
"uni-datetime-picker.cancel": "取消", |
|||
"uni-calender.SUN": "日", |
|||
"uni-calender.MON": "一", |
|||
"uni-calender.TUE": "二", |
|||
"uni-calender.WED": "三", |
|||
"uni-calender.THU": "四", |
|||
"uni-calender.FRI": "五", |
|||
"uni-calender.SAT": "六" |
|||
} |
|||
@ -0,0 +1,19 @@ |
|||
{ |
|||
"uni-datetime-picker.selectDate": "選擇日期", |
|||
"uni-datetime-picker.selectTime": "選擇時間", |
|||
"uni-datetime-picker.selectDateTime": "選擇日期時間", |
|||
"uni-datetime-picker.startDate": "開始日期", |
|||
"uni-datetime-picker.endDate": "結束日期", |
|||
"uni-datetime-picker.startTime": "開始时间", |
|||
"uni-datetime-picker.endTime": "結束时间", |
|||
"uni-datetime-picker.ok": "確定", |
|||
"uni-datetime-picker.clear": "清除", |
|||
"uni-datetime-picker.cancel": "取消", |
|||
"uni-calender.SUN": "日", |
|||
"uni-calender.MON": "一", |
|||
"uni-calender.TUE": "二", |
|||
"uni-calender.WED": "三", |
|||
"uni-calender.THU": "四", |
|||
"uni-calender.FRI": "五", |
|||
"uni-calender.SAT": "六" |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
// #ifdef H5
|
|||
export default { |
|||
name: 'Keypress', |
|||
props: { |
|||
disable: { |
|||
type: Boolean, |
|||
default: false |
|||
} |
|||
}, |
|||
mounted () { |
|||
const keyNames = { |
|||
esc: ['Esc', 'Escape'], |
|||
tab: 'Tab', |
|||
enter: 'Enter', |
|||
space: [' ', 'Spacebar'], |
|||
up: ['Up', 'ArrowUp'], |
|||
left: ['Left', 'ArrowLeft'], |
|||
right: ['Right', 'ArrowRight'], |
|||
down: ['Down', 'ArrowDown'], |
|||
delete: ['Backspace', 'Delete', 'Del'] |
|||
} |
|||
const listener = ($event) => { |
|||
if (this.disable) { |
|||
return |
|||
} |
|||
const keyName = Object.keys(keyNames).find(key => { |
|||
const keyName = $event.key |
|||
const value = keyNames[key] |
|||
return value === keyName || (Array.isArray(value) && value.includes(keyName)) |
|||
}) |
|||
if (keyName) { |
|||
// 避免和其他按键事件冲突
|
|||
setTimeout(() => { |
|||
this.$emit(keyName, {}) |
|||
}, 0) |
|||
} |
|||
} |
|||
document.addEventListener('keyup', listener) |
|||
this.$once('hook:beforeDestroy', () => { |
|||
document.removeEventListener('keyup', listener) |
|||
}) |
|||
}, |
|||
render: () => {} |
|||
} |
|||
// #endif
|
|||
@ -0,0 +1,927 @@ |
|||
<template> |
|||
<view class="uni-datetime-picker"> |
|||
<view @click="initTimePicker"> |
|||
<slot> |
|||
<view class="uni-datetime-picker-timebox-pointer" |
|||
:class="{'uni-datetime-picker-disabled': disabled, 'uni-datetime-picker-timebox': border}"> |
|||
<text class="uni-datetime-picker-text">{{time}}</text> |
|||
<view v-if="!time" class="uni-datetime-picker-time"> |
|||
<text class="uni-datetime-picker-text">{{selectTimeText}}</text> |
|||
</view> |
|||
</view> |
|||
</slot> |
|||
</view> |
|||
<view v-if="visible" id="mask" class="uni-datetime-picker-mask" @click="tiggerTimePicker"></view> |
|||
<view v-if="visible" class="uni-datetime-picker-popup" :class="[dateShow && timeShow ? '' : 'fix-nvue-height']" |
|||
:style="fixNvueBug"> |
|||
<view class="uni-title"> |
|||
<text class="uni-datetime-picker-text">{{selectTimeText}}</text> |
|||
</view> |
|||
<view v-if="dateShow" class="uni-datetime-picker__container-box"> |
|||
<picker-view class="uni-datetime-picker-view" :indicator-style="indicatorStyle" :value="ymd" |
|||
@change="bindDateChange"> |
|||
<picker-view-column> |
|||
<view class="uni-datetime-picker-item" v-for="(item,index) in years" :key="index"> |
|||
<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text> |
|||
</view> |
|||
</picker-view-column> |
|||
<picker-view-column> |
|||
<view class="uni-datetime-picker-item" v-for="(item,index) in months" :key="index"> |
|||
<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text> |
|||
</view> |
|||
</picker-view-column> |
|||
<picker-view-column> |
|||
<view class="uni-datetime-picker-item" v-for="(item,index) in days" :key="index"> |
|||
<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text> |
|||
</view> |
|||
</picker-view-column> |
|||
</picker-view> |
|||
<!-- 兼容 nvue 不支持伪类 --> |
|||
<text class="uni-datetime-picker-sign sign-left">-</text> |
|||
<text class="uni-datetime-picker-sign sign-right">-</text> |
|||
</view> |
|||
<view v-if="timeShow" class="uni-datetime-picker__container-box"> |
|||
<picker-view class="uni-datetime-picker-view" :class="[hideSecond ? 'time-hide-second' : '']" |
|||
:indicator-style="indicatorStyle" :value="hms" @change="bindTimeChange"> |
|||
<picker-view-column> |
|||
<view class="uni-datetime-picker-item" v-for="(item,index) in hours" :key="index"> |
|||
<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text> |
|||
</view> |
|||
</picker-view-column> |
|||
<picker-view-column> |
|||
<view class="uni-datetime-picker-item" v-for="(item,index) in minutes" :key="index"> |
|||
<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text> |
|||
</view> |
|||
</picker-view-column> |
|||
<picker-view-column v-if="!hideSecond"> |
|||
<view class="uni-datetime-picker-item" v-for="(item,index) in seconds" :key="index"> |
|||
<text class="uni-datetime-picker-item">{{lessThanTen(item)}}</text> |
|||
</view> |
|||
</picker-view-column> |
|||
</picker-view> |
|||
<!-- 兼容 nvue 不支持伪类 --> |
|||
<text class="uni-datetime-picker-sign" :class="[hideSecond ? 'sign-center' : 'sign-left']">:</text> |
|||
<text v-if="!hideSecond" class="uni-datetime-picker-sign sign-right">:</text> |
|||
</view> |
|||
<view class="uni-datetime-picker-btn"> |
|||
<view @click="clearTime"> |
|||
<text class="uni-datetime-picker-btn-text">{{clearText}}</text> |
|||
</view> |
|||
<view class="uni-datetime-picker-btn-group"> |
|||
<view class="uni-datetime-picker-cancel" @click="tiggerTimePicker"> |
|||
<text class="uni-datetime-picker-btn-text">{{cancelText}}</text> |
|||
</view> |
|||
<view @click="setTime"> |
|||
<text class="uni-datetime-picker-btn-text">{{okText}}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<!-- #ifdef H5 --> |
|||
<!-- <keypress v-if="visible" @esc="tiggerTimePicker" @enter="setTime" /> --> |
|||
<!-- #endif --> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
// #ifdef H5 |
|||
import keypress from './keypress' |
|||
// #endif |
|||
import { |
|||
initVueI18n |
|||
} from '@dcloudio/uni-i18n' |
|||
import messages from './i18n/index.js' |
|||
const { t } = initVueI18n(messages) |
|||
|
|||
/** |
|||
* DatetimePicker 时间选择器 |
|||
* @description 可以同时选择日期和时间的选择器 |
|||
* @tutorial https://ext.dcloud.net.cn/plugin?id=xxx |
|||
* @property {String} type = [datetime | date | time] 显示模式 |
|||
* @property {Boolean} multiple = [true|false] 是否多选 |
|||
* @property {String|Number} value 默认值 |
|||
* @property {String|Number} start 起始日期或时间 |
|||
* @property {String|Number} end 起始日期或时间 |
|||
* @property {String} return-type = [timestamp | string] |
|||
* @event {Function} change 选中发生变化触发 |
|||
*/ |
|||
|
|||
export default { |
|||
name: 'UniDatetimePicker', |
|||
components: { |
|||
// #ifdef H5 |
|||
keypress |
|||
// #endif |
|||
}, |
|||
data() { |
|||
return { |
|||
indicatorStyle: `height: 50px;`, |
|||
visible: false, |
|||
fixNvueBug: {}, |
|||
dateShow: true, |
|||
timeShow: true, |
|||
title: '日期和时间', |
|||
// 输入框当前时间 |
|||
time: '', |
|||
// 当前的年月日时分秒 |
|||
year: 1920, |
|||
month: 0, |
|||
day: 0, |
|||
hour: 0, |
|||
minute: 0, |
|||
second: 0, |
|||
// 起始时间 |
|||
startYear: 1920, |
|||
startMonth: 1, |
|||
startDay: 1, |
|||
startHour: 0, |
|||
startMinute: 0, |
|||
startSecond: 0, |
|||
// 结束时间 |
|||
endYear: 2120, |
|||
endMonth: 12, |
|||
endDay: 31, |
|||
endHour: 23, |
|||
endMinute: 59, |
|||
endSecond: 59, |
|||
} |
|||
}, |
|||
props: { |
|||
type: { |
|||
type: String, |
|||
default: 'datetime' |
|||
}, |
|||
value: { |
|||
type: [String, Number], |
|||
default: '' |
|||
}, |
|||
modelValue: { |
|||
type: [String, Number], |
|||
default: '' |
|||
}, |
|||
start: { |
|||
type: [Number, String], |
|||
default: '' |
|||
}, |
|||
end: { |
|||
type: [Number, String], |
|||
default: '' |
|||
}, |
|||
returnType: { |
|||
type: String, |
|||
default: 'string' |
|||
}, |
|||
disabled: { |
|||
type: [Boolean, String], |
|||
default: false |
|||
}, |
|||
border: { |
|||
type: [Boolean, String], |
|||
default: true |
|||
}, |
|||
hideSecond: { |
|||
type: [Boolean, String], |
|||
default: false |
|||
} |
|||
}, |
|||
watch: { |
|||
value: { |
|||
handler(newVal, oldVal) { |
|||
if (newVal) { |
|||
this.parseValue(this.fixIosDateFormat(newVal)) //兼容 iOS、safari 日期格式 |
|||
this.initTime(false) |
|||
} else { |
|||
this.time = '' |
|||
this.parseValue(Date.now()) |
|||
} |
|||
}, |
|||
immediate: true |
|||
}, |
|||
type: { |
|||
handler(newValue) { |
|||
if (newValue === 'date') { |
|||
this.dateShow = true |
|||
this.timeShow = false |
|||
this.title = '日期' |
|||
} else if (newValue === 'time') { |
|||
this.dateShow = false |
|||
this.timeShow = true |
|||
this.title = '时间' |
|||
} else { |
|||
this.dateShow = true |
|||
this.timeShow = true |
|||
this.title = '日期和时间' |
|||
} |
|||
}, |
|||
immediate: true |
|||
}, |
|||
start: { |
|||
handler(newVal) { |
|||
this.parseDatetimeRange(this.fixIosDateFormat(newVal), 'start') //兼容 iOS、safari 日期格式 |
|||
}, |
|||
immediate: true |
|||
}, |
|||
end: { |
|||
handler(newVal) { |
|||
this.parseDatetimeRange(this.fixIosDateFormat(newVal), 'end') //兼容 iOS、safari 日期格式 |
|||
}, |
|||
immediate: true |
|||
}, |
|||
|
|||
// 月、日、时、分、秒可选范围变化后,检查当前值是否在范围内,不在则当前值重置为可选范围第一项 |
|||
months(newVal) { |
|||
this.checkValue('month', this.month, newVal) |
|||
}, |
|||
days(newVal) { |
|||
this.checkValue('day', this.day, newVal) |
|||
}, |
|||
hours(newVal) { |
|||
this.checkValue('hour', this.hour, newVal) |
|||
}, |
|||
minutes(newVal) { |
|||
this.checkValue('minute', this.minute, newVal) |
|||
}, |
|||
seconds(newVal) { |
|||
this.checkValue('second', this.second, newVal) |
|||
} |
|||
}, |
|||
computed: { |
|||
// 当前年、月、日、时、分、秒选择范围 |
|||
years() { |
|||
return this.getCurrentRange('year') |
|||
}, |
|||
|
|||
months() { |
|||
return this.getCurrentRange('month') |
|||
}, |
|||
|
|||
days() { |
|||
return this.getCurrentRange('day') |
|||
}, |
|||
|
|||
hours() { |
|||
return this.getCurrentRange('hour') |
|||
}, |
|||
|
|||
minutes() { |
|||
return this.getCurrentRange('minute') |
|||
}, |
|||
|
|||
seconds() { |
|||
return this.getCurrentRange('second') |
|||
}, |
|||
|
|||
// picker 当前值数组 |
|||
ymd() { |
|||
return [this.year - this.minYear, this.month - this.minMonth, this.day - this.minDay] |
|||
}, |
|||
hms() { |
|||
return [this.hour - this.minHour, this.minute - this.minMinute, this.second - this.minSecond] |
|||
}, |
|||
|
|||
// 当前 date 是 start |
|||
currentDateIsStart() { |
|||
return this.year === this.startYear && this.month === this.startMonth && this.day === this.startDay |
|||
}, |
|||
|
|||
// 当前 date 是 end |
|||
currentDateIsEnd() { |
|||
return this.year === this.endYear && this.month === this.endMonth && this.day === this.endDay |
|||
}, |
|||
|
|||
// 当前年、月、日、时、分、秒的最小值和最大值 |
|||
minYear() { |
|||
return this.startYear |
|||
}, |
|||
maxYear() { |
|||
return this.endYear |
|||
}, |
|||
minMonth() { |
|||
if (this.year === this.startYear) { |
|||
return this.startMonth |
|||
} else { |
|||
return 1 |
|||
} |
|||
}, |
|||
maxMonth() { |
|||
if (this.year === this.endYear) { |
|||
return this.endMonth |
|||
} else { |
|||
return 12 |
|||
} |
|||
}, |
|||
minDay() { |
|||
if (this.year === this.startYear && this.month === this.startMonth) { |
|||
return this.startDay |
|||
} else { |
|||
return 1 |
|||
} |
|||
}, |
|||
maxDay() { |
|||
if (this.year === this.endYear && this.month === this.endMonth) { |
|||
return this.endDay |
|||
} else { |
|||
return this.daysInMonth(this.year, this.month) |
|||
} |
|||
}, |
|||
minHour() { |
|||
if (this.type === 'datetime') { |
|||
if (this.currentDateIsStart) { |
|||
return this.startHour |
|||
} else { |
|||
return 0 |
|||
} |
|||
} |
|||
if (this.type === 'time') { |
|||
return this.startHour |
|||
} |
|||
}, |
|||
maxHour() { |
|||
if (this.type === 'datetime') { |
|||
if (this.currentDateIsEnd) { |
|||
return this.endHour |
|||
} else { |
|||
return 23 |
|||
} |
|||
} |
|||
if (this.type === 'time') { |
|||
return this.endHour |
|||
} |
|||
}, |
|||
minMinute() { |
|||
if (this.type === 'datetime') { |
|||
if (this.currentDateIsStart && this.hour === this.startHour) { |
|||
return this.startMinute |
|||
} else { |
|||
return 0 |
|||
} |
|||
} |
|||
if (this.type === 'time') { |
|||
if (this.hour === this.startHour) { |
|||
return this.startMinute |
|||
} else { |
|||
return 0 |
|||
} |
|||
} |
|||
}, |
|||
maxMinute() { |
|||
if (this.type === 'datetime') { |
|||
if (this.currentDateIsEnd && this.hour === this.endHour) { |
|||
return this.endMinute |
|||
} else { |
|||
return 59 |
|||
} |
|||
} |
|||
if (this.type === 'time') { |
|||
if (this.hour === this.endHour) { |
|||
return this.endMinute |
|||
} else { |
|||
return 59 |
|||
} |
|||
} |
|||
}, |
|||
minSecond() { |
|||
if (this.type === 'datetime') { |
|||
if (this.currentDateIsStart && this.hour === this.startHour && this.minute === this.startMinute) { |
|||
return this.startSecond |
|||
} else { |
|||
return 0 |
|||
} |
|||
} |
|||
if (this.type === 'time') { |
|||
if (this.hour === this.startHour && this.minute === this.startMinute) { |
|||
return this.startSecond |
|||
} else { |
|||
return 0 |
|||
} |
|||
} |
|||
}, |
|||
maxSecond() { |
|||
if (this.type === 'datetime') { |
|||
if (this.currentDateIsEnd && this.hour === this.endHour && this.minute === this.endMinute) { |
|||
return this.endSecond |
|||
} else { |
|||
return 59 |
|||
} |
|||
} |
|||
if (this.type === 'time') { |
|||
if (this.hour === this.endHour && this.minute === this.endMinute) { |
|||
return this.endSecond |
|||
} else { |
|||
return 59 |
|||
} |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* for i18n |
|||
*/ |
|||
selectTimeText() { |
|||
return t("uni-datetime-picker.selectTime") |
|||
}, |
|||
okText() { |
|||
return t("uni-datetime-picker.ok") |
|||
}, |
|||
clearText() { |
|||
return t("uni-datetime-picker.clear") |
|||
}, |
|||
cancelText() { |
|||
return t("uni-datetime-picker.cancel") |
|||
} |
|||
}, |
|||
|
|||
mounted() { |
|||
// #ifdef APP-NVUE |
|||
const res = uni.getSystemInfoSync(); |
|||
this.fixNvueBug = { |
|||
top: res.windowHeight / 2, |
|||
left: res.windowWidth / 2 |
|||
} |
|||
// #endif |
|||
}, |
|||
|
|||
methods: { |
|||
/** |
|||
* @param {Object} item |
|||
* 小于 10 在前面加个 0 |
|||
*/ |
|||
|
|||
lessThanTen(item) { |
|||
return item < 10 ? '0' + item : item |
|||
}, |
|||
|
|||
/** |
|||
* 解析时分秒字符串,例如:00:00:00 |
|||
* @param {String} timeString |
|||
*/ |
|||
parseTimeType(timeString) { |
|||
if (timeString) { |
|||
let timeArr = timeString.split(':') |
|||
this.hour = Number(timeArr[0]) |
|||
this.minute = Number(timeArr[1]) |
|||
this.second = Number(timeArr[2]) |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 解析选择器初始值,类型可以是字符串、时间戳,例如:2000-10-02、'08:30:00'、 1610695109000 |
|||
* @param {String | Number} datetime |
|||
*/ |
|||
initPickerValue(datetime) { |
|||
let defaultValue = null |
|||
if (datetime) { |
|||
defaultValue = this.compareValueWithStartAndEnd(datetime, this.start, this.end) |
|||
} else { |
|||
defaultValue = Date.now() |
|||
defaultValue = this.compareValueWithStartAndEnd(defaultValue, this.start, this.end) |
|||
} |
|||
this.parseValue(defaultValue) |
|||
}, |
|||
|
|||
/** |
|||
* 初始值规则: |
|||
* - 用户设置初始值 value |
|||
* - 设置了起始时间 start、终止时间 end,并 start < value < end,初始值为 value, 否则初始值为 start |
|||
* - 只设置了起始时间 start,并 start < value,初始值为 value,否则初始值为 start |
|||
* - 只设置了终止时间 end,并 value < end,初始值为 value,否则初始值为 end |
|||
* - 无起始终止时间,则初始值为 value |
|||
* - 无初始值 value,则初始值为当前本地时间 Date.now() |
|||
* @param {Object} value |
|||
* @param {Object} dateBase |
|||
*/ |
|||
compareValueWithStartAndEnd(value, start, end) { |
|||
let winner = null |
|||
value = this.superTimeStamp(value) |
|||
start = this.superTimeStamp(start) |
|||
end = this.superTimeStamp(end) |
|||
|
|||
if (start && end) { |
|||
if (value < start) { |
|||
winner = new Date(start) |
|||
} else if (value > end) { |
|||
winner = new Date(end) |
|||
} else { |
|||
winner = new Date(value) |
|||
} |
|||
} else if (start && !end) { |
|||
winner = start <= value ? new Date(value) : new Date(start) |
|||
} else if (!start && end) { |
|||
winner = value <= end ? new Date(value) : new Date(end) |
|||
} else { |
|||
winner = new Date(value) |
|||
} |
|||
|
|||
return winner |
|||
}, |
|||
|
|||
/** |
|||
* 转换为可比较的时间戳,接受日期、时分秒、时间戳 |
|||
* @param {Object} value |
|||
*/ |
|||
superTimeStamp(value) { |
|||
let dateBase = '' |
|||
if (this.type === 'time' && value && typeof value === 'string') { |
|||
const now = new Date() |
|||
const year = now.getFullYear() |
|||
const month = now.getMonth() + 1 |
|||
const day = now.getDate() |
|||
dateBase = year + '/' + month + '/' + day + ' ' |
|||
} |
|||
if (Number(value) && typeof value !== NaN) { |
|||
value = parseInt(value) |
|||
dateBase = 0 |
|||
} |
|||
return this.createTimeStamp(dateBase + value) |
|||
}, |
|||
|
|||
/** |
|||
* 解析默认值 value,字符串、时间戳 |
|||
* @param {Object} defaultTime |
|||
*/ |
|||
parseValue(value) { |
|||
if (!value) { |
|||
return |
|||
} |
|||
if (this.type === 'time' && typeof value === "string") { |
|||
this.parseTimeType(value) |
|||
} else { |
|||
let defaultDate = null |
|||
defaultDate = new Date(value) |
|||
if (this.type !== 'time') { |
|||
this.year = defaultDate.getFullYear() |
|||
this.month = defaultDate.getMonth() + 1 |
|||
this.day = defaultDate.getDate() |
|||
} |
|||
if (this.type !== 'date') { |
|||
this.hour = defaultDate.getHours() |
|||
this.minute = defaultDate.getMinutes() |
|||
this.second = defaultDate.getSeconds() |
|||
} |
|||
} |
|||
if (this.hideSecond) { |
|||
this.second = 0 |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 解析可选择时间范围 start、end,年月日字符串、时间戳 |
|||
* @param {Object} defaultTime |
|||
*/ |
|||
parseDatetimeRange(point, pointType) { |
|||
// 时间为空,则重置为初始值 |
|||
if (!point) { |
|||
if (pointType === 'start') { |
|||
this.startYear = 1920 |
|||
this.startMonth = 1 |
|||
this.startDay = 1 |
|||
this.startHour = 0 |
|||
this.startMinute = 0 |
|||
this.startSecond = 0 |
|||
} |
|||
if (pointType === 'end') { |
|||
this.endYear = 2120 |
|||
this.endMonth = 12 |
|||
this.endDay = 31 |
|||
this.endHour = 23 |
|||
this.endMinute = 59 |
|||
this.endSecond = 59 |
|||
} |
|||
return |
|||
} |
|||
if (this.type === 'time') { |
|||
const pointArr = point.split(':') |
|||
this[pointType + 'Hour'] = Number(pointArr[0]) |
|||
this[pointType + 'Minute'] = Number(pointArr[1]) |
|||
this[pointType + 'Second'] = Number(pointArr[2]) |
|||
} else { |
|||
if (!point) { |
|||
pointType === 'start' ? this.startYear = this.year - 60 : this.endYear = this.year + 60 |
|||
return |
|||
} |
|||
if (Number(point) && Number(point) !== NaN) { |
|||
point = parseInt(point) |
|||
} |
|||
// datetime 的 end 没有时分秒, 则不限制 |
|||
const hasTime = /[0-9]:[0-9]/ |
|||
if (this.type === 'datetime' && pointType === 'end' && typeof point === 'string' && !hasTime.test( |
|||
point)) { |
|||
point = point + ' 23:59:59' |
|||
} |
|||
const pointDate = new Date(point) |
|||
this[pointType + 'Year'] = pointDate.getFullYear() |
|||
this[pointType + 'Month'] = pointDate.getMonth() + 1 |
|||
this[pointType + 'Day'] = pointDate.getDate() |
|||
if (this.type === 'datetime') { |
|||
this[pointType + 'Hour'] = pointDate.getHours() |
|||
this[pointType + 'Minute'] = pointDate.getMinutes() |
|||
this[pointType + 'Second'] = pointDate.getSeconds() |
|||
} |
|||
} |
|||
}, |
|||
|
|||
// 获取 年、月、日、时、分、秒 当前可选范围 |
|||
getCurrentRange(value) { |
|||
const range = [] |
|||
for (let i = this['min' + this.capitalize(value)]; i <= this['max' + this.capitalize(value)]; i++) { |
|||
range.push(i) |
|||
} |
|||
return range |
|||
}, |
|||
|
|||
// 字符串首字母大写 |
|||
capitalize(str) { |
|||
return str.charAt(0).toUpperCase() + str.slice(1) |
|||
}, |
|||
|
|||
// 检查当前值是否在范围内,不在则当前值重置为可选范围第一项 |
|||
checkValue(name, value, values) { |
|||
if (values.indexOf(value) === -1) { |
|||
this[name] = values[0] |
|||
} |
|||
}, |
|||
|
|||
// 每个月的实际天数 |
|||
daysInMonth(year, month) { // Use 1 for January, 2 for February, etc. |
|||
return new Date(year, month, 0).getDate(); |
|||
}, |
|||
|
|||
//兼容 iOS、safari 日期格式 |
|||
fixIosDateFormat(value) { |
|||
if (typeof value === 'string') { |
|||
value = value.replace(/-/g, '/') |
|||
} |
|||
return value |
|||
}, |
|||
|
|||
/** |
|||
* 生成时间戳 |
|||
* @param {Object} time |
|||
*/ |
|||
createTimeStamp(time) { |
|||
if (!time) return |
|||
if (typeof time === "number") { |
|||
return time |
|||
} else { |
|||
time = time.replace(/-/g, '/') |
|||
if (this.type === 'date') { |
|||
time = time + ' ' + '00:00:00' |
|||
} |
|||
return Date.parse(time) |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 生成日期或时间的字符串 |
|||
*/ |
|||
createDomSting() { |
|||
const yymmdd = this.year + |
|||
'-' + |
|||
this.lessThanTen(this.month) + |
|||
'-' + |
|||
this.lessThanTen(this.day) |
|||
|
|||
let hhmmss = this.lessThanTen(this.hour) + |
|||
':' + |
|||
this.lessThanTen(this.minute) |
|||
|
|||
if (!this.hideSecond) { |
|||
hhmmss = hhmmss + ':' + this.lessThanTen(this.second) |
|||
} |
|||
|
|||
if (this.type === 'date') { |
|||
return yymmdd |
|||
} else if (this.type === 'time') { |
|||
return hhmmss |
|||
} else { |
|||
return yymmdd + ' ' + hhmmss |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 初始化返回值,并抛出 change 事件 |
|||
*/ |
|||
initTime(emit = true) { |
|||
this.time = this.createDomSting() |
|||
if (!emit) return |
|||
if (this.returnType === 'timestamp' && this.type !== 'time') { |
|||
this.$emit('change', this.createTimeStamp(this.time)) |
|||
this.$emit('input', this.createTimeStamp(this.time)) |
|||
this.$emit('update:modelValue', this.createTimeStamp(this.time)) |
|||
} else { |
|||
this.$emit('change', this.time) |
|||
this.$emit('input', this.time) |
|||
this.$emit('update:modelValue', this.time) |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 用户选择日期或时间更新 data |
|||
* @param {Object} e |
|||
*/ |
|||
bindDateChange(e) { |
|||
const val = e.detail.value |
|||
this.year = this.years[val[0]] |
|||
this.month = this.months[val[1]] |
|||
this.day = this.days[val[2]] |
|||
}, |
|||
bindTimeChange(e) { |
|||
const val = e.detail.value |
|||
this.hour = this.hours[val[0]] |
|||
this.minute = this.minutes[val[1]] |
|||
this.second = this.seconds[val[2]] |
|||
}, |
|||
|
|||
/** |
|||
* 初始化弹出层 |
|||
*/ |
|||
initTimePicker() { |
|||
if (this.disabled) return |
|||
const value = this.fixIosDateFormat(this.value) |
|||
this.initPickerValue(value) |
|||
this.visible = !this.visible |
|||
}, |
|||
|
|||
/** |
|||
* 触发或关闭弹框 |
|||
*/ |
|||
tiggerTimePicker(e) { |
|||
this.visible = !this.visible |
|||
}, |
|||
|
|||
/** |
|||
* 用户点击“清空”按钮,清空当前值 |
|||
*/ |
|||
clearTime() { |
|||
this.time = '' |
|||
this.$emit('change', this.time) |
|||
this.$emit('input', this.time) |
|||
this.$emit('update:modelValue', this.time) |
|||
this.tiggerTimePicker() |
|||
}, |
|||
|
|||
/** |
|||
* 用户点击“确定”按钮 |
|||
*/ |
|||
setTime() { |
|||
this.initTime() |
|||
this.tiggerTimePicker() |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
.uni-datetime-picker { |
|||
/* #ifndef APP-NVUE */ |
|||
/* width: 100%; */ |
|||
/* #endif */ |
|||
} |
|||
|
|||
.uni-datetime-picker-view { |
|||
height: 130px; |
|||
width: 270px; |
|||
/* #ifndef APP-NVUE */ |
|||
cursor: pointer; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.uni-datetime-picker-item { |
|||
height: 50px; |
|||
line-height: 50px; |
|||
text-align: center; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.uni-datetime-picker-btn { |
|||
margin-top: 60px; |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
cursor: pointer; |
|||
/* #endif */ |
|||
flex-direction: row; |
|||
justify-content: space-between; |
|||
} |
|||
|
|||
.uni-datetime-picker-btn-text { |
|||
font-size: 14px; |
|||
color: #007AFF; |
|||
} |
|||
|
|||
.uni-datetime-picker-btn-group { |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
flex-direction: row; |
|||
} |
|||
|
|||
.uni-datetime-picker-cancel { |
|||
margin-right: 30px; |
|||
} |
|||
|
|||
.uni-datetime-picker-mask { |
|||
position: fixed; |
|||
bottom: 0px; |
|||
top: 0px; |
|||
left: 0px; |
|||
right: 0px; |
|||
background-color: rgba(0, 0, 0, 0.4); |
|||
transition-duration: 0.3s; |
|||
z-index: 998; |
|||
} |
|||
|
|||
.uni-datetime-picker-popup { |
|||
border-radius: 8px; |
|||
padding: 30px; |
|||
width: 270px; |
|||
/* #ifdef APP-NVUE */ |
|||
height: 500px; |
|||
/* #endif */ |
|||
/* #ifdef APP-NVUE */ |
|||
width: 330px; |
|||
/* #endif */ |
|||
background-color: #fff; |
|||
position: fixed; |
|||
top: 50%; |
|||
left: 50%; |
|||
transform: translate(-50%, -50%); |
|||
transition-duration: 0.3s; |
|||
z-index: 999; |
|||
} |
|||
|
|||
.fix-nvue-height { |
|||
/* #ifdef APP-NVUE */ |
|||
height: 330px; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.uni-datetime-picker-time { |
|||
color: grey; |
|||
} |
|||
|
|||
.uni-datetime-picker-column { |
|||
height: 50px; |
|||
} |
|||
|
|||
.uni-datetime-picker-timebox { |
|||
|
|||
border: 1px solid #E5E5E5; |
|||
border-radius: 5px; |
|||
padding: 7px 10px; |
|||
/* #ifndef APP-NVUE */ |
|||
box-sizing: border-box; |
|||
cursor: pointer; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.uni-datetime-picker-timebox-pointer { |
|||
/* #ifndef APP-NVUE */ |
|||
cursor: pointer; |
|||
/* #endif */ |
|||
} |
|||
|
|||
|
|||
.uni-datetime-picker-disabled { |
|||
opacity: 0.4; |
|||
/* #ifdef H5 */ |
|||
cursor: not-allowed !important; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.uni-datetime-picker-text { |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.uni-datetime-picker-sign { |
|||
position: absolute; |
|||
top: 53px; |
|||
/* 减掉 10px 的元素高度,兼容nvue */ |
|||
color: #999; |
|||
/* #ifdef APP-NVUE */ |
|||
font-size: 16px; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.sign-left { |
|||
left: 86px; |
|||
} |
|||
|
|||
.sign-right { |
|||
right: 86px; |
|||
} |
|||
|
|||
.sign-center { |
|||
left: 135px; |
|||
} |
|||
|
|||
.uni-datetime-picker__container-box { |
|||
position: relative; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
margin-top: 40px; |
|||
} |
|||
|
|||
.time-hide-second { |
|||
width: 180px; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,981 @@ |
|||
<template> |
|||
<view class="uni-date"> |
|||
<view class="uni-date-editor" @click="show"> |
|||
<slot> |
|||
<view class="uni-date-editor--x" :class="{'uni-date-editor--x__disabled': disabled, |
|||
'uni-date-x--border': border}"> |
|||
<view v-if="!isRange" class="uni-date-x uni-date-single"> |
|||
<uni-icons type="calendar" color="#e1e1e1" size="22"></uni-icons> |
|||
<input class="uni-date__x-input" type="text" v-model="singleVal" |
|||
:placeholder="singlePlaceholderText" :disabled="true" /> |
|||
</view> |
|||
<view v-else class="uni-date-x uni-date-range"> |
|||
<uni-icons type="calendar" color="#e1e1e1" size="22"></uni-icons> |
|||
<input class="uni-date__x-input t-c" type="text" v-model="range.startDate" |
|||
:placeholder="startPlaceholderText" :disabled="true" /> |
|||
<slot> |
|||
<view class="">{{rangeSeparator}}</view> |
|||
</slot> |
|||
<input class="uni-date__x-input t-c" type="text" v-model="range.endDate" |
|||
:placeholder="endPlaceholderText" :disabled="true" /> |
|||
</view> |
|||
<view v-if="showClearIcon" class="uni-date__icon-clear" @click.stop="clear"> |
|||
<uni-icons type="clear" color="#e1e1e1" size="18"></uni-icons> |
|||
</view> |
|||
</view> |
|||
</slot> |
|||
</view> |
|||
|
|||
<view v-show="popup" class="uni-date-mask" @click="close"></view> |
|||
<view v-if="!isPhone" ref="datePicker" v-show="popup" class="uni-date-picker__container"> |
|||
<view v-if="!isRange" class="uni-date-single--x" :style="popover"> |
|||
<view class="uni-popper__arrow"></view> |
|||
<view v-if="hasTime" class="uni-date-changed popup-x-header"> |
|||
<input class="uni-date__input t-c" type="text" v-model="tempSingleDate" |
|||
:placeholder="selectDateText" /> |
|||
<time-picker type="time" v-model="time" :border="false" :disabled="!tempSingleDate" |
|||
:start="reactStartTime" :end="reactEndTime" :hideSecond="hideSecond" style="width: 100%;"> |
|||
<input class="uni-date__input t-c" type="text" v-model="time" :placeholder="selectTimeText" |
|||
:disabled="!tempSingleDate" /> |
|||
</time-picker> |
|||
</view> |
|||
<calendar ref="pcSingle" :showMonth="false" |
|||
:start-date="caleRange.startDate" :end-date="caleRange.endDate" :date="defSingleDate" |
|||
@change="singleChange" style="padding: 0 8px;" /> |
|||
<view v-if="hasTime" class="popup-x-footer"> |
|||
<!-- <text class="">此刻</text> --> |
|||
<text class="confirm" @click="confirmSingleChange">{{okText}}</text> |
|||
</view> |
|||
<view class="uni-date-popper__arrow"></view> |
|||
</view> |
|||
|
|||
<view v-else class="uni-date-range--x" :style="popover"> |
|||
<view class="uni-popper__arrow"></view> |
|||
<view v-if="hasTime" class="popup-x-header uni-date-changed"> |
|||
<view class="popup-x-header--datetime"> |
|||
<input class="uni-date__input uni-date-range__input" type="text" v-model="tempRange.startDate" |
|||
:placeholder="startDateText" /> |
|||
<time-picker type="time" v-model="tempRange.startTime" :start="reactStartTime" :border="false" |
|||
:disabled="!tempRange.startDate" :hideSecond="hideSecond"> |
|||
<input class="uni-date__input uni-date-range__input" type="text" |
|||
v-model="tempRange.startTime" :placeholder="startTimeText" |
|||
:disabled="!tempRange.startDate" /> |
|||
</time-picker> |
|||
</view> |
|||
<uni-icons type="arrowthinright" color="#999" style="line-height: 40px;"></uni-icons> |
|||
<view class="popup-x-header--datetime"> |
|||
<input class="uni-date__input uni-date-range__input" type="text" v-model="tempRange.endDate" |
|||
:placeholder="endDateText" /> |
|||
<time-picker type="time" v-model="tempRange.endTime" :end="reactEndTime" :border="false" |
|||
:disabled="!tempRange.endDate" :hideSecond="hideSecond"> |
|||
<input class="uni-date__input uni-date-range__input" type="text" v-model="tempRange.endTime" |
|||
:placeholder="endTimeText" :disabled="!tempRange.endDate" /> |
|||
</time-picker> |
|||
</view> |
|||
</view> |
|||
<view class="popup-x-body"> |
|||
<calendar ref="left" :showMonth="false" |
|||
:start-date="caleRange.startDate" :end-date="caleRange.endDate" :range="true" |
|||
@change="leftChange" :pleStatus="endMultipleStatus" @firstEnterCale="updateRightCale" |
|||
@monthSwitch="leftMonthSwitch" style="padding: 0 8px;" /> |
|||
<calendar ref="right" :showMonth="false" |
|||
:start-date="caleRange.startDate" :end-date="caleRange.endDate" :range="true" |
|||
@change="rightChange" :pleStatus="startMultipleStatus" @firstEnterCale="updateLeftCale" |
|||
@monthSwitch="rightMonthSwitch" style="padding: 0 8px;border-left: 1px solid #F1F1F1;" /> |
|||
</view> |
|||
<view v-if="hasTime" class="popup-x-footer"> |
|||
<text class="" @click="clear">{{clearText}}</text> |
|||
<text class="confirm" @click="confirmRangeChange">{{okText}}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
<calendar v-show="isPhone" ref="mobile" :clearDate="false" :date="defSingleDate" :defTime="reactMobDefTime" |
|||
:start-date="caleRange.startDate" :end-date="caleRange.endDate" :selectableTimes="mobSelectableTime" |
|||
:pleStatus="endMultipleStatus" :showMonth="false" :range="isRange" :typeHasTime="hasTime" :insert="false" |
|||
:hideSecond="hideSecond" @confirm="mobileChange" /> |
|||
</view> |
|||
</template> |
|||
<script> |
|||
/** |
|||
* DatetimePicker 时间选择器 |
|||
* @description 同时支持 PC 和移动端使用日历选择日期和日期范围 |
|||
* @tutorial https://ext.dcloud.net.cn/plugin?id=3962 |
|||
* @property {String} type 选择器类型 |
|||
* @property {String|Number|Array|Date} value 绑定值 |
|||
* @property {String} placeholder 单选择时的占位内容 |
|||
* @property {String} start 起始时间 |
|||
* @property {String} end 终止时间 |
|||
* @property {String} start-placeholder 范围选择时开始日期的占位内容 |
|||
* @property {String} end-placeholder 范围选择时结束日期的占位内容 |
|||
* @property {String} range-separator 选择范围时的分隔符 |
|||
* @property {Boolean} border = [true|false] 是否有边框 |
|||
* @property {Boolean} disabled = [true|false] 是否禁用 |
|||
* @property {Boolean} clearIcon = [true|false] 是否显示清除按钮(仅PC端适用) |
|||
* @event {Function} change 确定日期时触发的事件 |
|||
* @event {Function} show 打开弹出层 |
|||
* @event {Function} close 关闭弹出层 |
|||
* @event {Function} clear 清除上次选中的状态和值 |
|||
**/ |
|||
import calendar from './calendar.vue' |
|||
import timePicker from './time-picker.vue' |
|||
import { |
|||
initVueI18n |
|||
} from '@dcloudio/uni-i18n' |
|||
import messages from './i18n/index.js' |
|||
const { |
|||
t |
|||
} = initVueI18n(messages) |
|||
|
|||
export default { |
|||
name: 'UniDatetimePicker', |
|||
components: { |
|||
calendar, |
|||
timePicker |
|||
}, |
|||
data() { |
|||
return { |
|||
isRange: false, |
|||
hasTime: false, |
|||
mobileRange: false, |
|||
// 单选 |
|||
singleVal: '', |
|||
tempSingleDate: '', |
|||
defSingleDate: '', |
|||
time: '', |
|||
// 范围选 |
|||
caleRange: { |
|||
startDate: '', |
|||
startTime: '', |
|||
endDate: '', |
|||
endTime: '' |
|||
}, |
|||
range: { |
|||
startDate: '', |
|||
// startTime: '', |
|||
endDate: '', |
|||
// endTime: '' |
|||
}, |
|||
tempRange: { |
|||
startDate: '', |
|||
startTime: '', |
|||
endDate: '', |
|||
endTime: '' |
|||
}, |
|||
// 左右日历同步数据 |
|||
startMultipleStatus: { |
|||
before: '', |
|||
after: '', |
|||
data: [], |
|||
fulldate: '' |
|||
}, |
|||
endMultipleStatus: { |
|||
before: '', |
|||
after: '', |
|||
data: [], |
|||
fulldate: '' |
|||
}, |
|||
visible: false, |
|||
popup: false, |
|||
popover: null, |
|||
isEmitValue: false, |
|||
isPhone: false, |
|||
isFirstShow: true, |
|||
} |
|||
}, |
|||
props: { |
|||
type: { |
|||
type: String, |
|||
default: 'datetime' |
|||
}, |
|||
value: { |
|||
type: [String, Number, Array, Date], |
|||
default: '' |
|||
}, |
|||
modelValue: { |
|||
type: [String, Number, Array, Date], |
|||
default: '' |
|||
}, |
|||
start: { |
|||
type: [Number, String], |
|||
default: '' |
|||
}, |
|||
end: { |
|||
type: [Number, String], |
|||
default: '' |
|||
}, |
|||
returnType: { |
|||
type: String, |
|||
default: 'string' |
|||
}, |
|||
placeholder: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
startPlaceholder: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
endPlaceholder: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
rangeSeparator: { |
|||
type: String, |
|||
default: '-' |
|||
}, |
|||
border: { |
|||
type: [Boolean], |
|||
default: true |
|||
}, |
|||
disabled: { |
|||
type: [Boolean], |
|||
default: false |
|||
}, |
|||
clearIcon: { |
|||
type: [Boolean], |
|||
default: true |
|||
}, |
|||
hideSecond: { |
|||
type: [Boolean], |
|||
default: false |
|||
} |
|||
}, |
|||
watch: { |
|||
type: { |
|||
immediate: true, |
|||
handler(newVal, oldVal) { |
|||
if (newVal.indexOf('time') !== -1) { |
|||
this.hasTime = true |
|||
} else { |
|||
this.hasTime = false |
|||
} |
|||
if (newVal.indexOf('range') !== -1) { |
|||
this.isRange = true |
|||
} else { |
|||
this.isRange = false |
|||
} |
|||
} |
|||
}, |
|||
value: { |
|||
immediate: true, |
|||
handler(newVal, oldVal) { |
|||
if (this.isEmitValue) { |
|||
this.isEmitValue = false |
|||
return |
|||
} |
|||
this.initPicker(newVal) |
|||
} |
|||
}, |
|||
|
|||
start: { |
|||
immediate: true, |
|||
handler(newVal, oldVal) { |
|||
if (!newVal) return |
|||
const { |
|||
defDate, |
|||
defTime |
|||
} = this.parseDate(newVal) |
|||
this.caleRange.startDate = defDate |
|||
if (this.hasTime) { |
|||
this.caleRange.startTime = defTime |
|||
} |
|||
} |
|||
}, |
|||
|
|||
end: { |
|||
immediate: true, |
|||
handler(newVal, oldVal) { |
|||
if (!newVal) return |
|||
const { |
|||
defDate, |
|||
defTime |
|||
} = this.parseDate(newVal) |
|||
this.caleRange.endDate = defDate |
|||
if (this.hasTime) { |
|||
this.caleRange.endTime = defTime |
|||
} |
|||
} |
|||
}, |
|||
}, |
|||
computed: { |
|||
reactStartTime() { |
|||
const activeDate = this.isRange ? this.tempRange.startDate : this.tempSingleDate |
|||
const res = activeDate === this.caleRange.startDate ? this.caleRange.startTime : '' |
|||
return res |
|||
}, |
|||
reactEndTime() { |
|||
const activeDate = this.isRange ? this.tempRange.endDate : this.tempSingleDate |
|||
const res = activeDate === this.caleRange.endDate ? this.caleRange.endTime : '' |
|||
return res |
|||
}, |
|||
reactMobDefTime() { |
|||
const times = { |
|||
start: this.tempRange.startTime, |
|||
end: this.tempRange.endTime |
|||
} |
|||
return this.isRange ? times : this.time |
|||
}, |
|||
mobSelectableTime() { |
|||
return { |
|||
start: this.caleRange.startTime, |
|||
end: this.caleRange.endTime |
|||
} |
|||
}, |
|||
datePopupWidth() { |
|||
// todo |
|||
return this.isRange ? 653 : 301 |
|||
}, |
|||
|
|||
/** |
|||
* for i18n |
|||
*/ |
|||
singlePlaceholderText() { |
|||
return this.placeholder || (this.type === 'date' ? this.selectDateText : t( |
|||
"uni-datetime-picker.selectDateTime")) |
|||
}, |
|||
startPlaceholderText() { |
|||
return this.startPlaceholder || this.startDateText |
|||
}, |
|||
endPlaceholderText() { |
|||
return this.endPlaceholder || this.endDateText |
|||
}, |
|||
selectDateText() { |
|||
return t("uni-datetime-picker.selectDate") |
|||
}, |
|||
selectTimeText() { |
|||
return t("uni-datetime-picker.selectTime") |
|||
}, |
|||
startDateText() { |
|||
return this.startPlaceholder || t("uni-datetime-picker.startDate") |
|||
}, |
|||
startTimeText() { |
|||
return t("uni-datetime-picker.startTime") |
|||
}, |
|||
endDateText() { |
|||
return this.endPlaceholder || t("uni-datetime-picker.endDate") |
|||
}, |
|||
endTimeText() { |
|||
return t("uni-datetime-picker.endTime") |
|||
}, |
|||
okText() { |
|||
return t("uni-datetime-picker.ok") |
|||
}, |
|||
clearText() { |
|||
return t("uni-datetime-picker.clear") |
|||
}, |
|||
showClearIcon() { |
|||
const { clearIcon, disabled, singleVal, range } = this |
|||
const bool = clearIcon && !disabled && (singleVal || (range.startDate && range.endDate)) |
|||
return bool |
|||
} |
|||
}, |
|||
created() { |
|||
this.form = this.getForm('uniForms') |
|||
this.formItem = this.getForm('uniFormsItem') |
|||
|
|||
// if (this.formItem) { |
|||
// if (this.formItem.name) { |
|||
// this.rename = this.formItem.name |
|||
// this.form.inputChildrens.push(this) |
|||
// } |
|||
// } |
|||
}, |
|||
mounted() { |
|||
this.platform() |
|||
}, |
|||
methods: { |
|||
/** |
|||
* 获取父元素实例 |
|||
*/ |
|||
getForm(name = 'uniForms') { |
|||
let parent = this.$parent; |
|||
let parentName = parent.$options.name; |
|||
while (parentName !== name) { |
|||
parent = parent.$parent; |
|||
if (!parent) return false |
|||
parentName = parent.$options.name; |
|||
} |
|||
return parent; |
|||
}, |
|||
initPicker(newVal) { |
|||
if (!newVal || Array.isArray(newVal) && !newVal.length) { |
|||
this.$nextTick(() => { |
|||
this.clear(false) |
|||
}) |
|||
return |
|||
} |
|||
if (!Array.isArray(newVal) && !this.isRange) { |
|||
const { |
|||
defDate, |
|||
defTime |
|||
} = this.parseDate(newVal) |
|||
this.singleVal = defDate |
|||
this.tempSingleDate = defDate |
|||
this.defSingleDate = defDate |
|||
if (this.hasTime) { |
|||
this.singleVal = defDate + ' ' + defTime |
|||
this.time = defTime |
|||
} |
|||
} else { |
|||
const [before, after] = newVal |
|||
if (!before && !after) return |
|||
const defBefore = this.parseDate(before) |
|||
const defAfter = this.parseDate(after) |
|||
const startDate = defBefore.defDate |
|||
const endDate = defAfter.defDate |
|||
this.range.startDate = this.tempRange.startDate = startDate |
|||
this.range.endDate = this.tempRange.endDate = endDate |
|||
|
|||
if (this.hasTime) { |
|||
this.range.startDate = defBefore.defDate + ' ' + defBefore.defTime |
|||
this.range.endDate = defAfter.defDate + ' ' + defAfter.defTime |
|||
this.tempRange.startTime = defBefore.defTime |
|||
this.tempRange.endTime = defAfter.defTime |
|||
} |
|||
const defaultRange = { |
|||
before: defBefore.defDate, |
|||
after: defAfter.defDate |
|||
} |
|||
this.startMultipleStatus = Object.assign({}, this.startMultipleStatus, defaultRange, { |
|||
which: 'right' |
|||
}) |
|||
this.endMultipleStatus = Object.assign({}, this.endMultipleStatus, defaultRange, { |
|||
which: 'left' |
|||
}) |
|||
} |
|||
}, |
|||
updateLeftCale(e) { |
|||
const left = this.$refs.left |
|||
// 设置范围选 |
|||
left.cale.setHoverMultiple(e.after) |
|||
left.setDate(this.$refs.left.nowDate.fullDate) |
|||
}, |
|||
updateRightCale(e) { |
|||
const right = this.$refs.right |
|||
// 设置范围选 |
|||
right.cale.setHoverMultiple(e.after) |
|||
right.setDate(this.$refs.right.nowDate.fullDate) |
|||
}, |
|||
platform() { |
|||
const systemInfo = uni.getSystemInfoSync() |
|||
this.isPhone = systemInfo.windowWidth <= 500 |
|||
this.windowWidth = systemInfo.windowWidth |
|||
}, |
|||
show(event) { |
|||
if (this.disabled) { |
|||
return |
|||
} |
|||
this.platform() |
|||
if (this.isPhone) { |
|||
this.$refs.mobile.open() |
|||
return |
|||
} |
|||
this.popover = { |
|||
top: '10px' |
|||
} |
|||
const dateEditor = uni.createSelectorQuery().in(this).select(".uni-date-editor") |
|||
dateEditor.boundingClientRect(rect => { |
|||
if (this.windowWidth - rect.left < this.datePopupWidth) { |
|||
this.popover.right = 0 |
|||
} |
|||
}).exec() |
|||
setTimeout(() => { |
|||
this.popup = !this.popup |
|||
if (!this.isPhone && this.isRange && this.isFirstShow) { |
|||
this.isFirstShow = false |
|||
const { |
|||
startDate, |
|||
endDate |
|||
} = this.range |
|||
if (startDate && endDate) { |
|||
if (this.diffDate(startDate, endDate) < 30) { |
|||
this.$refs.right.next() |
|||
} |
|||
} else { |
|||
this.$refs.right.next() |
|||
this.$refs.right.cale.lastHover = false |
|||
} |
|||
} |
|||
|
|||
}, 50) |
|||
}, |
|||
|
|||
close() { |
|||
setTimeout(() => { |
|||
this.popup = false |
|||
this.$emit('maskClick', this.value) |
|||
}, 20) |
|||
}, |
|||
setEmit(value) { |
|||
if (this.returnType === "timestamp" || this.returnType === "date") { |
|||
if (!Array.isArray(value)) { |
|||
if (!this.hasTime) { |
|||
value = value + ' ' + '00:00:00' |
|||
} |
|||
value = this.createTimestamp(value) |
|||
if (this.returnType === "date") { |
|||
value = new Date(value) |
|||
} |
|||
} else { |
|||
if (!this.hasTime) { |
|||
value[0] = value[0] + ' ' + '00:00:00' |
|||
value[1] = value[1] + ' ' + '00:00:00' |
|||
} |
|||
value[0] = this.createTimestamp(value[0]) |
|||
value[1] = this.createTimestamp(value[1]) |
|||
if (this.returnType === "date") { |
|||
value[0] = new Date(value[0]) |
|||
value[1] = new Date(value[1]) |
|||
} |
|||
} |
|||
} |
|||
this.formItem && this.formItem.setValue(value) |
|||
this.$emit('change', value) |
|||
this.$emit('input', value) |
|||
this.$emit('update:modelValue', value) |
|||
this.isEmitValue = true |
|||
}, |
|||
createTimestamp(date) { |
|||
date = this.fixIosDateFormat(date) |
|||
return Date.parse(new Date(date)) |
|||
}, |
|||
singleChange(e) { |
|||
this.tempSingleDate = e.fulldate |
|||
if (this.hasTime) return |
|||
this.confirmSingleChange() |
|||
}, |
|||
|
|||
confirmSingleChange() { |
|||
if (!this.tempSingleDate) { |
|||
this.popup = false |
|||
return |
|||
} |
|||
if (this.hasTime) { |
|||
this.singleVal = this.tempSingleDate + ' ' + (this.time ? this.time : '00:00:00') |
|||
} else { |
|||
this.singleVal = this.tempSingleDate |
|||
} |
|||
this.setEmit(this.singleVal) |
|||
this.popup = false |
|||
}, |
|||
|
|||
leftChange(e) { |
|||
const { |
|||
before, |
|||
after |
|||
} = e.range |
|||
this.rangeChange(before, after) |
|||
const obj = { |
|||
before: e.range.before, |
|||
after: e.range.after, |
|||
data: e.range.data, |
|||
fulldate: e.fulldate |
|||
} |
|||
this.startMultipleStatus = Object.assign({}, this.startMultipleStatus, obj) |
|||
}, |
|||
|
|||
rightChange(e) { |
|||
const { |
|||
before, |
|||
after |
|||
} = e.range |
|||
this.rangeChange(before, after) |
|||
const obj = { |
|||
before: e.range.before, |
|||
after: e.range.after, |
|||
data: e.range.data, |
|||
fulldate: e.fulldate |
|||
} |
|||
this.endMultipleStatus = Object.assign({}, this.endMultipleStatus, obj) |
|||
}, |
|||
|
|||
mobileChange(e) { |
|||
if (this.isRange) { |
|||
const { |
|||
before, |
|||
after |
|||
} = e.range |
|||
this.handleStartAndEnd(before, after, true) |
|||
if (this.hasTime) { |
|||
const { |
|||
startTime, |
|||
endTime |
|||
} = e.timeRange |
|||
this.tempRange.startTime = startTime |
|||
this.tempRange.endTime = endTime |
|||
} |
|||
this.confirmRangeChange() |
|||
|
|||
} else { |
|||
if (this.hasTime) { |
|||
this.singleVal = e.fulldate + ' ' + e.time |
|||
} else { |
|||
this.singleVal = e.fulldate |
|||
} |
|||
this.setEmit(this.singleVal) |
|||
} |
|||
this.$refs.mobile.close() |
|||
}, |
|||
|
|||
rangeChange(before, after) { |
|||
if (!(before && after)) return |
|||
this.handleStartAndEnd(before, after, true) |
|||
if (this.hasTime) return |
|||
this.confirmRangeChange() |
|||
}, |
|||
|
|||
confirmRangeChange() { |
|||
if (!this.tempRange.startDate && !this.tempRange.endDate) { |
|||
this.popup = false |
|||
return |
|||
} |
|||
let start, end |
|||
if (!this.hasTime) { |
|||
start = this.range.startDate = this.tempRange.startDate |
|||
end = this.range.endDate = this.tempRange.endDate |
|||
} else { |
|||
start = this.range.startDate = this.tempRange.startDate + ' ' + |
|||
(this.tempRange.startTime ? this.tempRange.startTime : '00:00:00') |
|||
end = this.range.endDate = this.tempRange.endDate + ' ' + |
|||
(this.tempRange.endTime ? this.tempRange.endTime : '00:00:00') |
|||
} |
|||
const displayRange = [start, end] |
|||
this.setEmit(displayRange) |
|||
this.popup = false |
|||
}, |
|||
|
|||
handleStartAndEnd(before, after, temp = false) { |
|||
if (!(before && after)) return |
|||
const type = temp ? 'tempRange' : 'range' |
|||
if (this.dateCompare(before, after)) { |
|||
this[type].startDate = before |
|||
this[type].endDate = after |
|||
} else { |
|||
this[type].startDate = after |
|||
this[type].endDate = before |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 比较时间大小 |
|||
*/ |
|||
dateCompare(startDate, endDate) { |
|||
// 计算截止时间 |
|||
startDate = new Date(startDate.replace('-', '/').replace('-', '/')) |
|||
// 计算详细项的截止时间 |
|||
endDate = new Date(endDate.replace('-', '/').replace('-', '/')) |
|||
if (startDate <= endDate) { |
|||
return true |
|||
} else { |
|||
return false |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 比较时间差 |
|||
*/ |
|||
diffDate(startDate, endDate) { |
|||
// 计算截止时间 |
|||
startDate = new Date(startDate.replace('-', '/').replace('-', '/')) |
|||
// 计算详细项的截止时间 |
|||
endDate = new Date(endDate.replace('-', '/').replace('-', '/')) |
|||
const diff = (endDate - startDate) / (24 * 60 * 60 * 1000) |
|||
return Math.abs(diff) |
|||
}, |
|||
|
|||
clear(needEmit = true) { |
|||
if (!this.isRange) { |
|||
this.singleVal = '' |
|||
this.tempSingleDate = '' |
|||
this.time = '' |
|||
if (this.isPhone) { |
|||
this.$refs.mobile && this.$refs.mobile.clearCalender() |
|||
} else { |
|||
this.$refs.pcSingle && this.$refs.pcSingle.clearCalender() |
|||
} |
|||
if (needEmit) { |
|||
this.formItem && this.formItem.setValue('') |
|||
this.$emit('change', '') |
|||
this.$emit('input', '') |
|||
this.$emit('update:modelValue', '') |
|||
} |
|||
} else { |
|||
this.range.startDate = '' |
|||
this.range.endDate = '' |
|||
this.tempRange.startDate = '' |
|||
this.tempRange.startTime = '' |
|||
this.tempRange.endDate = '' |
|||
this.tempRange.endTime = '' |
|||
if (this.isPhone) { |
|||
this.$refs.mobile && this.$refs.mobile.clearCalender() |
|||
} else { |
|||
this.$refs.left && this.$refs.left.clearCalender() |
|||
this.$refs.right && this.$refs.right.clearCalender() |
|||
this.$refs.right && this.$refs.right.next() |
|||
} |
|||
if (needEmit) { |
|||
this.formItem && this.formItem.setValue([]) |
|||
this.$emit('change', []) |
|||
this.$emit('input', []) |
|||
this.$emit('update:modelValue', []) |
|||
} |
|||
} |
|||
}, |
|||
|
|||
parseDate(date) { |
|||
date = this.fixIosDateFormat(date) |
|||
const defVal = new Date(date) |
|||
const year = defVal.getFullYear() |
|||
const month = defVal.getMonth() + 1 |
|||
const day = defVal.getDate() |
|||
const hour = defVal.getHours() |
|||
const minute = defVal.getMinutes() |
|||
const second = defVal.getSeconds() |
|||
const defDate = year + '-' + this.lessTen(month) + '-' + this.lessTen(day) |
|||
const defTime = this.lessTen(hour) + ':' + this.lessTen(minute) + (this.hideSecond ? '' : (':' + this |
|||
.lessTen(second))) |
|||
return { |
|||
defDate, |
|||
defTime |
|||
} |
|||
}, |
|||
|
|||
lessTen(item) { |
|||
return item < 10 ? '0' + item : item |
|||
}, |
|||
|
|||
//兼容 iOS、safari 日期格式 |
|||
fixIosDateFormat(value) { |
|||
if (typeof value === 'string') { |
|||
value = value.replace(/-/g, '/') |
|||
} |
|||
return value |
|||
}, |
|||
|
|||
leftMonthSwitch(e) { |
|||
// console.log('leftMonthSwitch 返回:', e) |
|||
}, |
|||
rightMonthSwitch(e) { |
|||
// console.log('rightMonthSwitch 返回:', e) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
.uni-date-x { |
|||
display: flex; |
|||
flex-direction: row; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 0 10px; |
|||
border-radius: 4px; |
|||
background-color: #fff; |
|||
color: #666; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.uni-date-x--border { |
|||
box-sizing: border-box; |
|||
border-radius: 4px; |
|||
border: 1px solid #dcdfe6; |
|||
} |
|||
|
|||
.uni-date-editor--x { |
|||
position: relative; |
|||
} |
|||
|
|||
.uni-date-editor--x .uni-date__icon-clear { |
|||
position: absolute; |
|||
top: 0; |
|||
right: 0; |
|||
display: inline-block; |
|||
box-sizing: border-box; |
|||
border: 9px solid transparent; |
|||
/* #ifdef H5 */ |
|||
cursor: pointer; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.uni-date__x-input { |
|||
padding: 0 8px; |
|||
height: 40px; |
|||
width: 100%; |
|||
line-height: 40px; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.t-c { |
|||
text-align: center; |
|||
} |
|||
|
|||
.uni-date__input { |
|||
height: 40px; |
|||
width: 100%; |
|||
line-height: 40px; |
|||
font-size: 14px; |
|||
} |
|||
|
|||
.uni-date-range__input { |
|||
text-align: center; |
|||
max-width: 142px; |
|||
} |
|||
|
|||
.uni-date-picker__container { |
|||
position: relative; |
|||
/* position: fixed; |
|||
left: 0; |
|||
right: 0; |
|||
top: 0; |
|||
bottom: 0; |
|||
box-sizing: border-box; |
|||
z-index: 996; |
|||
font-size: 14px; */ |
|||
} |
|||
|
|||
.uni-date-mask { |
|||
position: fixed; |
|||
bottom: 0px; |
|||
top: 0px; |
|||
left: 0px; |
|||
right: 0px; |
|||
background-color: rgba(0, 0, 0, 0); |
|||
transition-duration: 0.3s; |
|||
z-index: 996; |
|||
} |
|||
|
|||
.uni-date-single--x { |
|||
/* padding: 0 8px; */ |
|||
background-color: #fff; |
|||
position: absolute; |
|||
top: 0; |
|||
z-index: 999; |
|||
border: 1px solid #EBEEF5; |
|||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
.uni-date-range--x { |
|||
/* padding: 0 8px; */ |
|||
background-color: #fff; |
|||
position: absolute; |
|||
top: 0; |
|||
z-index: 999; |
|||
border: 1px solid #EBEEF5; |
|||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); |
|||
border-radius: 4px; |
|||
} |
|||
|
|||
.uni-date-editor--x__disabled { |
|||
opacity: 0.4; |
|||
cursor: default; |
|||
} |
|||
|
|||
.uni-date-editor--logo { |
|||
width: 16px; |
|||
height: 16px; |
|||
vertical-align: middle; |
|||
} |
|||
|
|||
/* 添加时间 */ |
|||
.popup-x-header { |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
flex-direction: row; |
|||
/* justify-content: space-between; */ |
|||
} |
|||
|
|||
.popup-x-header--datetime { |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
flex-direction: row; |
|||
flex: 1; |
|||
} |
|||
|
|||
.popup-x-body { |
|||
display: flex; |
|||
} |
|||
|
|||
.popup-x-footer { |
|||
padding: 0 15px; |
|||
border-top-color: #F1F1F1; |
|||
border-top-style: solid; |
|||
border-top-width: 1px; |
|||
/* background-color: #fff; */ |
|||
line-height: 40px; |
|||
text-align: right; |
|||
color: #666; |
|||
} |
|||
|
|||
.popup-x-footer text:hover { |
|||
color: #007aff; |
|||
cursor: pointer; |
|||
opacity: 0.8; |
|||
} |
|||
|
|||
.popup-x-footer .confirm { |
|||
margin-left: 20px; |
|||
color: #007aff; |
|||
} |
|||
|
|||
.uni-date-changed { |
|||
/* background-color: #fff; */ |
|||
text-align: center; |
|||
color: #333; |
|||
border-bottom-color: #F1F1F1; |
|||
border-bottom-style: solid; |
|||
border-bottom-width: 1px; |
|||
/* padding: 0 50px; */ |
|||
} |
|||
|
|||
.uni-date-changed--time text { |
|||
/* padding: 0 20px; */ |
|||
height: 50px; |
|||
line-height: 50px; |
|||
} |
|||
|
|||
.uni-date-changed .uni-date-changed--time { |
|||
/* display: flex; */ |
|||
flex: 1; |
|||
} |
|||
|
|||
.uni-date-changed--time-date { |
|||
color: #333; |
|||
opacity: 0.6; |
|||
} |
|||
|
|||
.mr-50 { |
|||
margin-right: 50px; |
|||
} |
|||
|
|||
/* picker 弹出层通用的指示小三角, todo:扩展至上下左右方向定位 */ |
|||
.uni-popper__arrow, |
|||
.uni-popper__arrow::after { |
|||
position: absolute; |
|||
display: block; |
|||
width: 0; |
|||
height: 0; |
|||
border-color: transparent; |
|||
border-style: solid; |
|||
border-width: 6px; |
|||
} |
|||
|
|||
.uni-popper__arrow { |
|||
filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03)); |
|||
top: -6px; |
|||
left: 10%; |
|||
margin-right: 3px; |
|||
border-top-width: 0; |
|||
border-bottom-color: #EBEEF5; |
|||
} |
|||
|
|||
.uni-popper__arrow::after { |
|||
content: " "; |
|||
top: 1px; |
|||
margin-left: -6px; |
|||
border-top-width: 0; |
|||
border-bottom-color: #fff; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,410 @@ |
|||
class Calendar { |
|||
constructor({ |
|||
date, |
|||
selected, |
|||
startDate, |
|||
endDate, |
|||
range, |
|||
// multipleStatus
|
|||
} = {}) { |
|||
// 当前日期
|
|||
this.date = this.getDate(new Date()) // 当前初入日期
|
|||
// 打点信息
|
|||
this.selected = selected || []; |
|||
// 范围开始
|
|||
this.startDate = startDate |
|||
// 范围结束
|
|||
this.endDate = endDate |
|||
this.range = range |
|||
// 多选状态
|
|||
this.cleanMultipleStatus() |
|||
// 每周日期
|
|||
this.weeks = {} |
|||
// this._getWeek(this.date.fullDate)
|
|||
// this.multipleStatus = multipleStatus
|
|||
this.lastHover = false |
|||
} |
|||
/** |
|||
* 设置日期 |
|||
* @param {Object} date |
|||
*/ |
|||
setDate(date) { |
|||
this.selectDate = this.getDate(date) |
|||
this._getWeek(this.selectDate.fullDate) |
|||
} |
|||
|
|||
/** |
|||
* 清理多选状态 |
|||
*/ |
|||
cleanMultipleStatus() { |
|||
this.multipleStatus = { |
|||
before: '', |
|||
after: '', |
|||
data: [] |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 重置开始日期 |
|||
*/ |
|||
resetSatrtDate(startDate) { |
|||
// 范围开始
|
|||
this.startDate = startDate |
|||
|
|||
} |
|||
|
|||
/** |
|||
* 重置结束日期 |
|||
*/ |
|||
resetEndDate(endDate) { |
|||
// 范围结束
|
|||
this.endDate = endDate |
|||
} |
|||
|
|||
/** |
|||
* 获取任意时间 |
|||
*/ |
|||
getDate(date, AddDayCount = 0, str = 'day') { |
|||
if (!date) { |
|||
date = new Date() |
|||
} |
|||
if (typeof date !== 'object') { |
|||
date = date.replace(/-/g, '/') |
|||
} |
|||
const dd = new Date(date) |
|||
switch (str) { |
|||
case 'day': |
|||
dd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期
|
|||
break |
|||
case 'month': |
|||
if (dd.getDate() === 31) { |
|||
dd.setDate(dd.getDate() + AddDayCount) |
|||
} else { |
|||
dd.setMonth(dd.getMonth() + AddDayCount) // 获取AddDayCount天后的日期
|
|||
} |
|||
break |
|||
case 'year': |
|||
dd.setFullYear(dd.getFullYear() + AddDayCount) // 获取AddDayCount天后的日期
|
|||
break |
|||
} |
|||
const y = dd.getFullYear() |
|||
const m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期,不足10补0
|
|||
const d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号,不足10补0
|
|||
return { |
|||
fullDate: y + '-' + m + '-' + d, |
|||
year: y, |
|||
month: m, |
|||
date: d, |
|||
day: dd.getDay() |
|||
} |
|||
} |
|||
|
|||
|
|||
/** |
|||
* 获取上月剩余天数 |
|||
*/ |
|||
_getLastMonthDays(firstDay, full) { |
|||
let dateArr = [] |
|||
for (let i = firstDay; i > 0; i--) { |
|||
const beforeDate = new Date(full.year, full.month - 1, -i + 1).getDate() |
|||
dateArr.push({ |
|||
date: beforeDate, |
|||
month: full.month - 1, |
|||
disable: true |
|||
}) |
|||
} |
|||
return dateArr |
|||
} |
|||
/** |
|||
* 获取本月天数 |
|||
*/ |
|||
_currentMonthDys(dateData, full) { |
|||
let dateArr = [] |
|||
let fullDate = this.date.fullDate |
|||
for (let i = 1; i <= dateData; i++) { |
|||
let isinfo = false |
|||
let nowDate = full.year + '-' + (full.month < 10 ? |
|||
full.month : full.month) + '-' + (i < 10 ? |
|||
'0' + i : i) |
|||
// 是否今天
|
|||
let isDay = fullDate === nowDate |
|||
// 获取打点信息
|
|||
let info = this.selected && this.selected.find((item) => { |
|||
if (this.dateEqual(nowDate, item.date)) { |
|||
return item |
|||
} |
|||
}) |
|||
|
|||
// 日期禁用
|
|||
let disableBefore = true |
|||
let disableAfter = true |
|||
if (this.startDate) { |
|||
// let dateCompBefore = this.dateCompare(this.startDate, fullDate)
|
|||
// disableBefore = this.dateCompare(dateCompBefore ? this.startDate : fullDate, nowDate)
|
|||
disableBefore = this.dateCompare(this.startDate, nowDate) |
|||
} |
|||
|
|||
if (this.endDate) { |
|||
// let dateCompAfter = this.dateCompare(fullDate, this.endDate)
|
|||
// disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate)
|
|||
disableAfter = this.dateCompare(nowDate, this.endDate) |
|||
} |
|||
let multiples = this.multipleStatus.data |
|||
let checked = false |
|||
let multiplesStatus = -1 |
|||
if (this.range) { |
|||
if (multiples) { |
|||
multiplesStatus = multiples.findIndex((item) => { |
|||
return this.dateEqual(item, nowDate) |
|||
}) |
|||
} |
|||
if (multiplesStatus !== -1) { |
|||
checked = true |
|||
} |
|||
} |
|||
let data = { |
|||
fullDate: nowDate, |
|||
year: full.year, |
|||
date: i, |
|||
multiple: this.range ? checked : false, |
|||
beforeMultiple: this.isLogicBefore(nowDate, this.multipleStatus.before, this.multipleStatus.after), |
|||
afterMultiple: this.isLogicAfter(nowDate, this.multipleStatus.before, this.multipleStatus.after), |
|||
month: full.month, |
|||
disable: !(disableBefore && disableAfter), |
|||
isDay, |
|||
userChecked: false |
|||
} |
|||
if (info) { |
|||
data.extraInfo = info |
|||
} |
|||
|
|||
dateArr.push(data) |
|||
} |
|||
return dateArr |
|||
} |
|||
/** |
|||
* 获取下月天数 |
|||
*/ |
|||
_getNextMonthDays(surplus, full) { |
|||
let dateArr = [] |
|||
for (let i = 1; i < surplus + 1; i++) { |
|||
dateArr.push({ |
|||
date: i, |
|||
month: Number(full.month) + 1, |
|||
disable: true |
|||
}) |
|||
} |
|||
return dateArr |
|||
} |
|||
|
|||
/** |
|||
* 获取当前日期详情 |
|||
* @param {Object} date |
|||
*/ |
|||
getInfo(date) { |
|||
if (!date) { |
|||
date = new Date() |
|||
} |
|||
const dateInfo = this.canlender.find(item => item.fullDate === this.getDate(date).fullDate) |
|||
return dateInfo |
|||
} |
|||
|
|||
/** |
|||
* 比较时间大小 |
|||
*/ |
|||
dateCompare(startDate, endDate) { |
|||
// 计算截止时间
|
|||
startDate = new Date(startDate.replace('-', '/').replace('-', '/')) |
|||
// 计算详细项的截止时间
|
|||
endDate = new Date(endDate.replace('-', '/').replace('-', '/')) |
|||
if (startDate <= endDate) { |
|||
return true |
|||
} else { |
|||
return false |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 比较时间是否相等 |
|||
*/ |
|||
dateEqual(before, after) { |
|||
// 计算截止时间
|
|||
before = new Date(before.replace('-', '/').replace('-', '/')) |
|||
// 计算详细项的截止时间
|
|||
after = new Date(after.replace('-', '/').replace('-', '/')) |
|||
if (before.getTime() - after.getTime() === 0) { |
|||
return true |
|||
} else { |
|||
return false |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 比较真实起始日期 |
|||
*/ |
|||
|
|||
isLogicBefore(currentDay, before, after) { |
|||
let logicBefore = before |
|||
if (before && after) { |
|||
logicBefore = this.dateCompare(before, after) ? before : after |
|||
} |
|||
return this.dateEqual(logicBefore, currentDay) |
|||
} |
|||
|
|||
isLogicAfter(currentDay, before, after) { |
|||
let logicAfter = after |
|||
if (before && after) { |
|||
logicAfter = this.dateCompare(before, after) ? after : before |
|||
} |
|||
return this.dateEqual(logicAfter, currentDay) |
|||
} |
|||
|
|||
/** |
|||
* 获取日期范围内所有日期 |
|||
* @param {Object} begin |
|||
* @param {Object} end |
|||
*/ |
|||
geDateAll(begin, end) { |
|||
var arr = [] |
|||
var ab = begin.split('-') |
|||
var ae = end.split('-') |
|||
var db = new Date() |
|||
db.setFullYear(ab[0], ab[1] - 1, ab[2]) |
|||
var de = new Date() |
|||
de.setFullYear(ae[0], ae[1] - 1, ae[2]) |
|||
var unixDb = db.getTime() - 24 * 60 * 60 * 1000 |
|||
var unixDe = de.getTime() - 24 * 60 * 60 * 1000 |
|||
for (var k = unixDb; k <= unixDe;) { |
|||
k = k + 24 * 60 * 60 * 1000 |
|||
arr.push(this.getDate(new Date(parseInt(k))).fullDate) |
|||
} |
|||
return arr |
|||
} |
|||
|
|||
/** |
|||
* 获取多选状态 |
|||
*/ |
|||
setMultiple(fullDate) { |
|||
let { |
|||
before, |
|||
after |
|||
} = this.multipleStatus |
|||
if (!this.range) return |
|||
if (before && after) { |
|||
if (!this.lastHover) { |
|||
this.lastHover = true |
|||
return |
|||
} |
|||
this.multipleStatus.before = fullDate |
|||
this.multipleStatus.after = '' |
|||
this.multipleStatus.data = [] |
|||
this.multipleStatus.fulldate = '' |
|||
this.lastHover = false |
|||
} else { |
|||
if (!before) { |
|||
this.multipleStatus.before = fullDate |
|||
this.lastHover = false |
|||
} else { |
|||
this.multipleStatus.after = fullDate |
|||
if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) { |
|||
this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus |
|||
.after); |
|||
} else { |
|||
this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus |
|||
.before); |
|||
} |
|||
this.lastHover = true |
|||
} |
|||
} |
|||
this._getWeek(fullDate) |
|||
} |
|||
|
|||
/** |
|||
* 鼠标 hover 更新多选状态 |
|||
*/ |
|||
setHoverMultiple(fullDate) { |
|||
let { |
|||
before, |
|||
after |
|||
} = this.multipleStatus |
|||
|
|||
if (!this.range) return |
|||
if (this.lastHover) return |
|||
|
|||
if (!before) { |
|||
this.multipleStatus.before = fullDate |
|||
} else { |
|||
this.multipleStatus.after = fullDate |
|||
if (this.dateCompare(this.multipleStatus.before, this.multipleStatus.after)) { |
|||
this.multipleStatus.data = this.geDateAll(this.multipleStatus.before, this.multipleStatus.after); |
|||
} else { |
|||
this.multipleStatus.data = this.geDateAll(this.multipleStatus.after, this.multipleStatus.before); |
|||
} |
|||
} |
|||
this._getWeek(fullDate) |
|||
} |
|||
|
|||
/** |
|||
* 更新默认值多选状态 |
|||
*/ |
|||
setDefaultMultiple(before, after) { |
|||
this.multipleStatus.before = before |
|||
this.multipleStatus.after = after |
|||
if (before && after) { |
|||
if (this.dateCompare(before, after)) { |
|||
this.multipleStatus.data = this.geDateAll(before, after); |
|||
this._getWeek(after) |
|||
} else { |
|||
this.multipleStatus.data = this.geDateAll(after, before); |
|||
this._getWeek(before) |
|||
} |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 获取每周数据 |
|||
* @param {Object} dateData |
|||
*/ |
|||
_getWeek(dateData) { |
|||
const { |
|||
fullDate, |
|||
year, |
|||
month, |
|||
date, |
|||
day |
|||
} = this.getDate(dateData) |
|||
let firstDay = new Date(year, month - 1, 1).getDay() |
|||
let currentDay = new Date(year, month, 0).getDate() |
|||
let dates = { |
|||
lastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天
|
|||
currentMonthDys: this._currentMonthDys(currentDay, this.getDate(dateData)), // 本月天数
|
|||
nextMonthDays: [], // 下个月开始几天
|
|||
weeks: [] |
|||
} |
|||
let canlender = [] |
|||
const surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length) |
|||
dates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData)) |
|||
canlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays) |
|||
let weeks = {} |
|||
// 拼接数组 上个月开始几天 + 本月天数+ 下个月开始几天
|
|||
for (let i = 0; i < canlender.length; i++) { |
|||
if (i % 7 === 0) { |
|||
weeks[parseInt(i / 7)] = new Array(7) |
|||
} |
|||
weeks[parseInt(i / 7)][i % 7] = canlender[i] |
|||
} |
|||
this.canlender = canlender |
|||
this.weeks = weeks |
|||
} |
|||
|
|||
//静态方法
|
|||
// static init(date) {
|
|||
// if (!this.instance) {
|
|||
// this.instance = new Calendar(date);
|
|||
// }
|
|||
// return this.instance;
|
|||
// }
|
|||
} |
|||
|
|||
|
|||
export default Calendar |
|||
@ -0,0 +1,90 @@ |
|||
{ |
|||
"id": "uni-datetime-picker", |
|||
"displayName": "uni-datetime-picker 日期选择器", |
|||
"version": "2.2.2", |
|||
"description": "uni-datetime-picker 日期时间选择器,支持日历,支持范围选择", |
|||
"keywords": [ |
|||
"uni-datetime-picker", |
|||
"uni-ui", |
|||
"uniui", |
|||
"日期时间选择器", |
|||
"日期时间" |
|||
], |
|||
"repository": "https://github.com/dcloudio/uni-ui", |
|||
"engines": { |
|||
"HBuilderX": "" |
|||
}, |
|||
"directories": { |
|||
"example": "../../temps/example_temps" |
|||
}, |
|||
"dcloudext": { |
|||
"category": [ |
|||
"前端组件", |
|||
"通用组件" |
|||
], |
|||
"sale": { |
|||
"regular": { |
|||
"price": "0.00" |
|||
}, |
|||
"sourcecode": { |
|||
"price": "0.00" |
|||
} |
|||
}, |
|||
"contact": { |
|||
"qq": "" |
|||
}, |
|||
"declaration": { |
|||
"ads": "无", |
|||
"data": "无", |
|||
"permissions": "无" |
|||
}, |
|||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" |
|||
}, |
|||
"uni_modules": { |
|||
"dependencies": [ |
|||
"uni-scss", |
|||
"uni-icons" |
|||
], |
|||
"encrypt": [], |
|||
"platforms": { |
|||
"cloud": { |
|||
"tcb": "y", |
|||
"aliyun": "y" |
|||
}, |
|||
"client": { |
|||
"App": { |
|||
"app-vue": "y", |
|||
"app-nvue": "n" |
|||
}, |
|||
"H5-mobile": { |
|||
"Safari": "y", |
|||
"Android Browser": "y", |
|||
"微信浏览器(Android)": "y", |
|||
"QQ浏览器(Android)": "y" |
|||
}, |
|||
"H5-pc": { |
|||
"Chrome": "y", |
|||
"IE": "y", |
|||
"Edge": "y", |
|||
"Firefox": "y", |
|||
"Safari": "y" |
|||
}, |
|||
"小程序": { |
|||
"微信": "y", |
|||
"阿里": "y", |
|||
"百度": "y", |
|||
"字节跳动": "y", |
|||
"QQ": "y" |
|||
}, |
|||
"快应用": { |
|||
"华为": "u", |
|||
"联盟": "u" |
|||
}, |
|||
"Vue": { |
|||
"vue2": "y", |
|||
"vue3": "y" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,21 @@ |
|||
|
|||
|
|||
> `重要通知:组件升级更新 2.0.0 后,支持日期+时间范围选择,组件 ui 将使用日历选择日期,ui 变化较大,同时支持 PC 和 移动端。此版本不向后兼容,不再支持单独的时间选择(type=time)及相关的 hide-second 属性(时间选可使用内置组件 picker)。若仍需使用旧版本,可在插件市场下载*非uni_modules版本*,旧版本将不再维护` |
|||
|
|||
## DatetimePicker 时间选择器 |
|||
|
|||
> **组件名:uni-datetime-picker** |
|||
> 代码块: `uDatetimePicker` |
|||
|
|||
|
|||
该组件的优势是,支持**时间戳**输入和输出(起始时间、终止时间也支持时间戳),可**同时选择**日期和时间。 |
|||
|
|||
若只是需要单独选择日期和时间,不需要时间戳输入和输出,可使用原生的 picker 组件。 |
|||
|
|||
**_点击 picker 默认值规则:_** |
|||
|
|||
- 若设置初始值 value, 会显示在 picker 显示框中 |
|||
- 若无初始值 value,则初始值 value 为当前本地时间 Date.now(), 但不会显示在 picker 显示框中 |
|||
|
|||
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-datetime-picker) |
|||
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 |
|||
@ -0,0 +1,21 @@ |
|||
## 1.2.0(2021-11-19) |
|||
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) |
|||
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-table](https://uniapp.dcloud.io/component/uniui/uni-table) |
|||
## 1.1.0(2021-07-30) |
|||
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) |
|||
## 1.0.7(2021-07-08) |
|||
- 新增 uni-th 支持 date 日期筛选范围 |
|||
## 1.0.6(2021-07-05) |
|||
- 新增 uni-th 支持 range 筛选范围 |
|||
## 1.0.5(2021-06-28) |
|||
- 新增 uni-th 筛选功能 |
|||
## 1.0.4(2021-05-12) |
|||
- 新增 示例地址 |
|||
- 修复 示例项目缺少组件的Bug |
|||
## 1.0.3(2021-04-16) |
|||
- 新增 sortable 属性,是否开启单列排序 |
|||
- 优化 表格多选逻辑 |
|||
## 1.0.2(2021-03-22) |
|||
- uni-tr 添加 disabled 属性,用于 type=selection 时,设置某行是否可由全选按钮控制 |
|||
## 1.0.1(2021-02-05) |
|||
- 调整为uni_modules目录规范 |
|||
@ -0,0 +1,455 @@ |
|||
<template> |
|||
<view class="uni-table-scroll" :class="{ 'table--border': border, 'border-none': !noData }"> |
|||
<!-- #ifdef H5 --> |
|||
<table class="uni-table" border="0" cellpadding="0" cellspacing="0" :class="{ 'table--stripe': stripe }" :style="{ 'min-width': minWidth + 'px' }"> |
|||
<slot></slot> |
|||
<view v-if="noData" class="uni-table-loading"> |
|||
<view class="uni-table-text" :class="{ 'empty-border': border }">{{ emptyText }}</view> |
|||
</view> |
|||
<view v-if="loading" class="uni-table-mask" :class="{ 'empty-border': border }"><div class="uni-table--loader"></div></view> |
|||
</table> |
|||
<!-- #endif --> |
|||
<!-- #ifndef H5 --> |
|||
<view class="uni-table" :style="{ 'min-width': minWidth + 'px' }" :class="{ 'table--stripe': stripe }"> |
|||
<slot></slot> |
|||
<view v-if="noData" class="uni-table-loading"> |
|||
<view class="uni-table-text" :class="{ 'empty-border': border }">{{ emptyText }}</view> |
|||
</view> |
|||
<view v-if="loading" class="uni-table-mask" :class="{ 'empty-border': border }"><div class="uni-table--loader"></div></view> |
|||
</view> |
|||
<!-- #endif --> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
/** |
|||
* Table 表格 |
|||
* @description 用于展示多条结构类似的数据 |
|||
* @tutorial https://ext.dcloud.net.cn/plugin?id=3270 |
|||
* @property {Boolean} border 是否带有纵向边框 |
|||
* @property {Boolean} stripe 是否显示斑马线 |
|||
* @property {Boolean} type 是否开启多选 |
|||
* @property {String} emptyText 空数据时显示的文本内容 |
|||
* @property {Boolean} loading 显示加载中 |
|||
* @event {Function} selection-change 开启多选时,当选择项发生变化时会触发该事件 |
|||
*/ |
|||
export default { |
|||
name: 'uniTable', |
|||
options: { |
|||
virtualHost: true |
|||
}, |
|||
emits:['selection-change'], |
|||
props: { |
|||
data: { |
|||
type: Array, |
|||
default() { |
|||
return [] |
|||
} |
|||
}, |
|||
// 是否有竖线 |
|||
border: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
// 是否显示斑马线 |
|||
stripe: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
// 多选 |
|||
type: { |
|||
type: String, |
|||
default: '' |
|||
}, |
|||
// 没有更多数据 |
|||
emptyText: { |
|||
type: String, |
|||
default: '没有更多数据' |
|||
}, |
|||
loading: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
rowKey: { |
|||
type: String, |
|||
default: '' |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
noData: true, |
|||
minWidth: 0, |
|||
multiTableHeads: [] |
|||
} |
|||
}, |
|||
watch: { |
|||
loading(val) {}, |
|||
data(newVal) { |
|||
let theadChildren = this.theadChildren |
|||
let rowspan = 1 |
|||
if (this.theadChildren) { |
|||
rowspan = this.theadChildren.rowspan |
|||
} |
|||
|
|||
// this.trChildren.length - rowspan |
|||
this.noData = false |
|||
// this.noData = newVal.length === 0 |
|||
} |
|||
}, |
|||
created() { |
|||
// 定义tr的实例数组 |
|||
this.trChildren = [] |
|||
this.thChildren = [] |
|||
this.theadChildren = null |
|||
this.backData = [] |
|||
this.backIndexData = [] |
|||
}, |
|||
|
|||
methods: { |
|||
isNodata() { |
|||
let theadChildren = this.theadChildren |
|||
let rowspan = 1 |
|||
if (this.theadChildren) { |
|||
rowspan = this.theadChildren.rowspan |
|||
} |
|||
this.noData = this.trChildren.length - rowspan <= 0 |
|||
}, |
|||
/** |
|||
* 选中所有 |
|||
*/ |
|||
selectionAll() { |
|||
let startIndex = 1 |
|||
let theadChildren = this.theadChildren |
|||
if (!this.theadChildren) { |
|||
theadChildren = this.trChildren[0] |
|||
} else { |
|||
startIndex = theadChildren.rowspan - 1 |
|||
} |
|||
let isHaveData = this.data && this.data.length.length > 0 |
|||
theadChildren.checked = true |
|||
theadChildren.indeterminate = false |
|||
this.trChildren.forEach((item, index) => { |
|||
if (!item.disabled) { |
|||
item.checked = true |
|||
if (isHaveData && item.keyValue) { |
|||
const row = this.data.find(v => v[this.rowKey] === item.keyValue) |
|||
if (!this.backData.find(v => v[this.rowKey] === row[this.rowKey])) { |
|||
this.backData.push(row) |
|||
} |
|||
} |
|||
if (index > (startIndex - 1) && this.backIndexData.indexOf(index - startIndex) === -1) { |
|||
this.backIndexData.push(index - startIndex) |
|||
} |
|||
} |
|||
}) |
|||
// this.backData = JSON.parse(JSON.stringify(this.data)) |
|||
this.$emit('selection-change', { |
|||
detail: { |
|||
value: this.backData, |
|||
index: this.backIndexData |
|||
} |
|||
}) |
|||
}, |
|||
/** |
|||
* 用于多选表格,切换某一行的选中状态,如果使用了第二个参数,则是设置这一行选中与否(selected 为 true 则选中) |
|||
*/ |
|||
toggleRowSelection(row, selected) { |
|||
// if (!this.theadChildren) return |
|||
row = [].concat(row) |
|||
|
|||
this.trChildren.forEach((item, index) => { |
|||
// if (item.keyValue) { |
|||
|
|||
const select = row.findIndex(v => { |
|||
// |
|||
if (typeof v === 'number') { |
|||
return v === index - 1 |
|||
} else { |
|||
return v[this.rowKey] === item.keyValue |
|||
} |
|||
}) |
|||
let ischeck = item.checked |
|||
if (select !== -1) { |
|||
if (typeof selected === 'boolean') { |
|||
item.checked = selected |
|||
} else { |
|||
item.checked = !item.checked |
|||
} |
|||
if (ischeck !== item.checked) { |
|||
this.check(item.rowData||item, item.checked, item.rowData?item.keyValue:null, true) |
|||
} |
|||
} |
|||
// } |
|||
}) |
|||
this.$emit('selection-change', { |
|||
detail: { |
|||
value: this.backData, |
|||
index:this.backIndexData |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 用于多选表格,清空用户的选择 |
|||
*/ |
|||
clearSelection() { |
|||
let theadChildren = this.theadChildren |
|||
if (!this.theadChildren) { |
|||
theadChildren = this.trChildren[0] |
|||
} |
|||
// if (!this.theadChildren) return |
|||
theadChildren.checked = false |
|||
theadChildren.indeterminate = false |
|||
this.trChildren.forEach(item => { |
|||
// if (item.keyValue) { |
|||
item.checked = false |
|||
// } |
|||
}) |
|||
this.backData = [] |
|||
this.backIndexData = [] |
|||
this.$emit('selection-change', { |
|||
detail: { |
|||
value: [], |
|||
index: [] |
|||
} |
|||
}) |
|||
}, |
|||
/** |
|||
* 用于多选表格,切换所有行的选中状态 |
|||
*/ |
|||
toggleAllSelection() { |
|||
let list = [] |
|||
let startIndex = 1 |
|||
let theadChildren = this.theadChildren |
|||
if (!this.theadChildren) { |
|||
theadChildren = this.trChildren[0] |
|||
} else { |
|||
startIndex = theadChildren.rowspan - 1 |
|||
} |
|||
this.trChildren.forEach((item, index) => { |
|||
if (!item.disabled) { |
|||
if (index > (startIndex - 1) ) { |
|||
list.push(index-startIndex) |
|||
} |
|||
} |
|||
}) |
|||
this.toggleRowSelection(list) |
|||
}, |
|||
|
|||
/** |
|||
* 选中\取消选中 |
|||
* @param {Object} child |
|||
* @param {Object} check |
|||
* @param {Object} rowValue |
|||
*/ |
|||
check(child, check, keyValue, emit) { |
|||
let theadChildren = this.theadChildren |
|||
if (!this.theadChildren) { |
|||
theadChildren = this.trChildren[0] |
|||
} |
|||
|
|||
|
|||
|
|||
let childDomIndex = this.trChildren.findIndex((item, index) => child === item) |
|||
if(childDomIndex < 0){ |
|||
childDomIndex = this.data.findIndex(v=>v[this.rowKey] === keyValue) + 1 |
|||
} |
|||
const dataLen = this.trChildren.filter(v => !v.disabled && v.keyValue).length |
|||
if (childDomIndex === 0) { |
|||
check ? this.selectionAll() : this.clearSelection() |
|||
return |
|||
} |
|||
|
|||
if (check) { |
|||
if (keyValue) { |
|||
this.backData.push(child) |
|||
} |
|||
this.backIndexData.push(childDomIndex - 1) |
|||
} else { |
|||
const index = this.backData.findIndex(v => v[this.rowKey] === keyValue) |
|||
const idx = this.backIndexData.findIndex(item => item === childDomIndex - 1) |
|||
if (keyValue) { |
|||
this.backData.splice(index, 1) |
|||
} |
|||
this.backIndexData.splice(idx, 1) |
|||
} |
|||
|
|||
const domCheckAll = this.trChildren.find((item, index) => index > 0 && !item.checked && !item.disabled) |
|||
if (!domCheckAll) { |
|||
theadChildren.indeterminate = false |
|||
theadChildren.checked = true |
|||
} else { |
|||
theadChildren.indeterminate = true |
|||
theadChildren.checked = false |
|||
} |
|||
|
|||
if (this.backIndexData.length === 0) { |
|||
theadChildren.indeterminate = false |
|||
} |
|||
|
|||
if (!emit) { |
|||
this.$emit('selection-change', { |
|||
detail: { |
|||
value: this.backData, |
|||
index: this.backIndexData |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
$border-color: #ebeef5; |
|||
|
|||
.uni-table-scroll { |
|||
width: 100%; |
|||
/* #ifndef APP-NVUE */ |
|||
overflow-x: auto; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.uni-table { |
|||
position: relative; |
|||
width: 100%; |
|||
border-radius: 5px; |
|||
// box-shadow: 0px 0px 3px 1px rgba(0, 0, 0, 0.1); |
|||
background-color: #fff; |
|||
/* #ifndef APP-NVUE */ |
|||
box-sizing: border-box; |
|||
display: table; |
|||
overflow-x: auto; |
|||
::v-deep .uni-table-tr:nth-child(n + 2) { |
|||
&:hover { |
|||
background-color: #f5f7fa; |
|||
} |
|||
} |
|||
::v-deep .uni-table-thead { |
|||
.uni-table-tr { |
|||
// background-color: #f5f7fa; |
|||
&:hover { |
|||
background-color:#fafafa; |
|||
} |
|||
} |
|||
} |
|||
/* #endif */ |
|||
} |
|||
|
|||
.table--border { |
|||
border: 1px $border-color solid; |
|||
border-right: none; |
|||
} |
|||
|
|||
.border-none { |
|||
/* #ifndef APP-NVUE */ |
|||
border-bottom: none; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.table--stripe { |
|||
/* #ifndef APP-NVUE */ |
|||
::v-deep .uni-table-tr:nth-child(2n + 3) { |
|||
background-color: #fafafa; |
|||
} |
|||
/* #endif */ |
|||
} |
|||
|
|||
/* 表格加载、无数据样式 */ |
|||
.uni-table-loading { |
|||
position: relative; |
|||
/* #ifndef APP-NVUE */ |
|||
display: table-row; |
|||
/* #endif */ |
|||
height: 50px; |
|||
line-height: 50px; |
|||
overflow: hidden; |
|||
box-sizing: border-box; |
|||
} |
|||
.empty-border { |
|||
border-right: 1px $border-color solid; |
|||
} |
|||
.uni-table-text { |
|||
position: absolute; |
|||
right: 0; |
|||
left: 0; |
|||
text-align: center; |
|||
font-size: 14px; |
|||
color: #999; |
|||
} |
|||
|
|||
.uni-table-mask { |
|||
position: absolute; |
|||
top: 0; |
|||
bottom: 0; |
|||
left: 0; |
|||
right: 0; |
|||
background-color: rgba(255, 255, 255, 0.8); |
|||
z-index: 99; |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
margin: auto; |
|||
transition: all 0.5s; |
|||
/* #endif */ |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
.uni-table--loader { |
|||
width: 30px; |
|||
height: 30px; |
|||
border: 2px solid #aaa; |
|||
// border-bottom-color: transparent; |
|||
border-radius: 50%; |
|||
/* #ifndef APP-NVUE */ |
|||
animation: 2s uni-table--loader linear infinite; |
|||
/* #endif */ |
|||
position: relative; |
|||
} |
|||
|
|||
@keyframes uni-table--loader { |
|||
0% { |
|||
transform: rotate(360deg); |
|||
} |
|||
|
|||
10% { |
|||
border-left-color: transparent; |
|||
} |
|||
|
|||
20% { |
|||
border-bottom-color: transparent; |
|||
} |
|||
|
|||
30% { |
|||
border-right-color: transparent; |
|||
} |
|||
|
|||
40% { |
|||
border-top-color: transparent; |
|||
} |
|||
|
|||
50% { |
|||
transform: rotate(0deg); |
|||
} |
|||
|
|||
60% { |
|||
border-top-color: transparent; |
|||
} |
|||
|
|||
70% { |
|||
border-left-color: transparent; |
|||
} |
|||
|
|||
80% { |
|||
border-bottom-color: transparent; |
|||
} |
|||
|
|||
90% { |
|||
border-right-color: transparent; |
|||
} |
|||
|
|||
100% { |
|||
transform: rotate(-360deg); |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,29 @@ |
|||
<template> |
|||
<!-- #ifdef H5 --> |
|||
<tbody> |
|||
<slot></slot> |
|||
</tbody> |
|||
<!-- #endif --> |
|||
<!-- #ifndef H5 --> |
|||
<view><slot></slot></view> |
|||
<!-- #endif --> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'uniBody', |
|||
options: { |
|||
virtualHost: true |
|||
}, |
|||
data() { |
|||
return { |
|||
|
|||
} |
|||
}, |
|||
created() {}, |
|||
methods: {} |
|||
} |
|||
</script> |
|||
|
|||
<style> |
|||
</style> |
|||
@ -0,0 +1,90 @@ |
|||
<template> |
|||
<!-- #ifdef H5 --> |
|||
<td class="uni-table-td" :rowspan="rowspan" :colspan="colspan" :class="{'table--border':border}" :style="{width:width + 'px','text-align':align}"> |
|||
<slot></slot> |
|||
</td> |
|||
<!-- #endif --> |
|||
<!-- #ifndef H5 --> |
|||
<!-- :class="{'table--border':border}" --> |
|||
<view class="uni-table-td" :class="{'table--border':border}" :style="{width:width + 'px','text-align':align}"> |
|||
<slot></slot> |
|||
</view> |
|||
<!-- #endif --> |
|||
|
|||
</template> |
|||
|
|||
<script> |
|||
/** |
|||
* Td 单元格 |
|||
* @description 表格中的标准单元格组件 |
|||
* @tutorial https://ext.dcloud.net.cn/plugin?id=3270 |
|||
* @property {Number} align = [left|center|right] 单元格对齐方式 |
|||
*/ |
|||
export default { |
|||
name: 'uniTd', |
|||
options: { |
|||
virtualHost: true |
|||
}, |
|||
props: { |
|||
width: { |
|||
type: [String, Number], |
|||
default: '' |
|||
}, |
|||
align: { |
|||
type: String, |
|||
default: 'left' |
|||
}, |
|||
rowspan: { |
|||
type: [Number,String], |
|||
default: 1 |
|||
}, |
|||
colspan: { |
|||
type: [Number,String], |
|||
default: 1 |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
border: false |
|||
}; |
|||
}, |
|||
created() { |
|||
this.root = this.getTable() |
|||
this.border = this.root.border |
|||
}, |
|||
methods: { |
|||
/** |
|||
* 获取父元素实例 |
|||
*/ |
|||
getTable() { |
|||
let parent = this.$parent; |
|||
let parentName = parent.$options.name; |
|||
while (parentName !== 'uniTable') { |
|||
parent = parent.$parent; |
|||
if (!parent) return false; |
|||
parentName = parent.$options.name; |
|||
} |
|||
return parent; |
|||
}, |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
$border-color:#EBEEF5; |
|||
|
|||
.uni-table-td { |
|||
display: table-cell; |
|||
padding: 8px 10px; |
|||
font-size: 14px; |
|||
border-bottom: 1px $border-color solid; |
|||
font-weight: 400; |
|||
color: #606266; |
|||
line-height: 23px; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.table--border { |
|||
border-right: 1px $border-color solid; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,503 @@ |
|||
<template> |
|||
<view class="uni-filter-dropdown"> |
|||
<view class="dropdown-btn" @click="onDropdown"> |
|||
<view class="icon-select" :class="{active: canReset}" v-if="isSelect || isRange"></view> |
|||
<view class="icon-search" :class="{active: canReset}" v-if="isSearch"> |
|||
<view class="icon-search-0"></view> |
|||
<view class="icon-search-1"></view> |
|||
</view> |
|||
<view class="icon-calendar" :class="{active: canReset}" v-if="isDate"> |
|||
<view class="icon-calendar-0"></view> |
|||
<view class="icon-calendar-1"></view> |
|||
</view> |
|||
</view> |
|||
<view class="uni-dropdown-cover" v-if="isOpened" @click="handleClose"></view> |
|||
<view class="dropdown-popup dropdown-popup-right" v-if="isOpened" @click.stop> |
|||
<!-- select--> |
|||
<view v-if="isSelect" class="list"> |
|||
<label class="flex-r a-i-c list-item" v-for="(item,index) in dataList" :key="index" |
|||
@click="onItemClick($event, index)"> |
|||
<check-box class="check" :checked="item.checked" /> |
|||
<view class="checklist-content"> |
|||
<text class="checklist-text" :style="item.styleIconText">{{item[map.text]}}</text> |
|||
</view> |
|||
</label> |
|||
</view> |
|||
<view v-if="isSelect" class="flex-r opera-area"> |
|||
<view class="flex-f btn btn-default" :class="{disable: !canReset}" @click="handleSelectReset"> |
|||
{{resource.reset}}</view> |
|||
<view class="flex-f btn btn-submit" @click="handleSelectSubmit">{{resource.submit}}</view> |
|||
</view> |
|||
<!-- search --> |
|||
<view v-if="isSearch" class="search-area"> |
|||
<input class="search-input" v-model="filterValue" /> |
|||
</view> |
|||
<view v-if="isSearch" class="flex-r opera-area"> |
|||
<view class="flex-f btn btn-submit" @click="handleSearchSubmit">{{resource.search}}</view> |
|||
<view class="flex-f btn btn-default" :class="{disable: !canReset}" @click="handleSearchReset"> |
|||
{{resource.reset}}</view> |
|||
</view> |
|||
<!-- range --> |
|||
<view v-if="isRange"> |
|||
<view class="input-label">{{resource.gt}}</view> |
|||
<input class="input" v-model="gtValue" /> |
|||
<view class="input-label">{{resource.lt}}</view> |
|||
<input class="input" v-model="ltValue" /> |
|||
</view> |
|||
<view v-if="isRange" class="flex-r opera-area"> |
|||
<view class="flex-f btn btn-default" :class="{disable: !canReset}" @click="handleRangeReset"> |
|||
{{resource.reset}}</view> |
|||
<view class="flex-f btn btn-submit" @click="handleRangeSubmit">{{resource.submit}}</view> |
|||
</view> |
|||
<!-- date --> |
|||
<view v-if="isDate"> |
|||
<uni-datetime-picker ref="datetimepicker" :value="dateRange" type="datetimerange" return-type="timestamp" @change="datetimechange" @maskClick="timepickerclose"> |
|||
<view></view> |
|||
</uni-datetime-picker> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import checkBox from '../uni-tr/table-checkbox.vue' |
|||
|
|||
const resource = { |
|||
"reset": "重置", |
|||
"search": "搜索", |
|||
"submit": "确定", |
|||
"filter": "筛选", |
|||
"gt": "大于等于", |
|||
"lt": "小于等于", |
|||
"date": "日期范围" |
|||
} |
|||
|
|||
const DropdownType = { |
|||
Select: "select", |
|||
Search: "search", |
|||
Range: "range", |
|||
Date: "date", |
|||
Timestamp: "timestamp" |
|||
} |
|||
|
|||
export default { |
|||
name: 'FilterDropdown', |
|||
emits:['change'], |
|||
components: { |
|||
checkBox |
|||
}, |
|||
options: { |
|||
virtualHost: true |
|||
}, |
|||
props: { |
|||
filterType: { |
|||
type: String, |
|||
default: DropdownType.Select |
|||
}, |
|||
filterData: { |
|||
type: Array, |
|||
default () { |
|||
return [] |
|||
} |
|||
}, |
|||
mode: { |
|||
type: String, |
|||
default: 'default' |
|||
}, |
|||
map: { |
|||
type: Object, |
|||
default () { |
|||
return { |
|||
text: 'text', |
|||
value: 'value' |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
computed: { |
|||
canReset() { |
|||
if (this.isSearch) { |
|||
return this.filterValue.length > 0 |
|||
} |
|||
if (this.isSelect) { |
|||
return this.checkedValues.length > 0 |
|||
} |
|||
if (this.isRange) { |
|||
return (this.gtValue.length > 0 && this.ltValue.length > 0) |
|||
} |
|||
if (this.isDate) { |
|||
return this.dateSelect.length > 0 |
|||
} |
|||
return false |
|||
}, |
|||
isSelect() { |
|||
return this.filterType === DropdownType.Select |
|||
}, |
|||
isSearch() { |
|||
return this.filterType === DropdownType.Search |
|||
}, |
|||
isRange() { |
|||
return this.filterType === DropdownType.Range |
|||
}, |
|||
isDate() { |
|||
return (this.filterType === DropdownType.Date || this.filterType === DropdownType.Timestamp) |
|||
} |
|||
}, |
|||
watch: { |
|||
filters(newVal) { |
|||
this._copyFilters() |
|||
}, |
|||
indeterminate(newVal) { |
|||
this.isIndeterminate = newVal |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
resource, |
|||
enabled: true, |
|||
isOpened: false, |
|||
dataList: [], |
|||
filterValue: '', |
|||
checkedValues: [], |
|||
gtValue: '', |
|||
ltValue: '', |
|||
dateRange: [], |
|||
dateSelect: [] |
|||
}; |
|||
}, |
|||
created() { |
|||
this._copyFilters() |
|||
}, |
|||
methods: { |
|||
_copyFilters() { |
|||
let dl = JSON.parse(JSON.stringify(this.filterData)) |
|||
for (let i = 0; i < dl.length; i++) { |
|||
if (dl[i].checked === undefined) { |
|||
dl[i].checked = false |
|||
} |
|||
} |
|||
this.dataList = dl |
|||
}, |
|||
openPopup() { |
|||
this.isOpened = true |
|||
if (this.isDate) { |
|||
this.$nextTick(() => { |
|||
if (!this.dateRange.length) { |
|||
this.resetDate() |
|||
} |
|||
this.$refs.datetimepicker.show() |
|||
}) |
|||
} |
|||
}, |
|||
closePopup() { |
|||
this.isOpened = false |
|||
}, |
|||
handleClose(e) { |
|||
this.closePopup() |
|||
}, |
|||
resetDate() { |
|||
let date = new Date() |
|||
let dateText = date.toISOString().split('T')[0] |
|||
this.dateRange = [dateText + ' 0:00:00', dateText + ' 23:59:59'] |
|||
}, |
|||
onDropdown(e) { |
|||
this.openPopup() |
|||
}, |
|||
onItemClick(e, index) { |
|||
let items = this.dataList |
|||
let listItem = items[index] |
|||
if (listItem.checked === undefined) { |
|||
items[index].checked = true |
|||
} else { |
|||
items[index].checked = !listItem.checked |
|||
} |
|||
|
|||
let checkvalues = [] |
|||
for (let i = 0; i < items.length; i++) { |
|||
const item = items[i] |
|||
if (item.checked) { |
|||
checkvalues.push(item.value) |
|||
} |
|||
} |
|||
this.checkedValues = checkvalues |
|||
}, |
|||
datetimechange(e) { |
|||
this.closePopup() |
|||
this.dateRange = e |
|||
this.dateSelect = e |
|||
this.$emit('change', { |
|||
filterType: this.filterType, |
|||
filter: e |
|||
}) |
|||
}, |
|||
timepickerclose(e) { |
|||
this.closePopup() |
|||
}, |
|||
handleSelectSubmit() { |
|||
this.closePopup() |
|||
this.$emit('change', { |
|||
filterType: this.filterType, |
|||
filter: this.checkedValues |
|||
}) |
|||
}, |
|||
handleSelectReset() { |
|||
if (!this.canReset) { |
|||
return; |
|||
} |
|||
var items = this.dataList |
|||
for (let i = 0; i < items.length; i++) { |
|||
let item = items[i] |
|||
this.$set(item, 'checked', false) |
|||
} |
|||
this.checkedValues = [] |
|||
this.handleSelectSubmit() |
|||
}, |
|||
handleSearchSubmit() { |
|||
this.closePopup() |
|||
this.$emit('change', { |
|||
filterType: this.filterType, |
|||
filter: this.filterValue |
|||
}) |
|||
}, |
|||
handleSearchReset() { |
|||
if (!this.canReset) { |
|||
return; |
|||
} |
|||
this.filterValue = '' |
|||
this.handleSearchSubmit() |
|||
}, |
|||
handleRangeSubmit(isReset) { |
|||
this.closePopup() |
|||
this.$emit('change', { |
|||
filterType: this.filterType, |
|||
filter: isReset === true ? [] : [parseInt(this.gtValue), parseInt(this.ltValue)] |
|||
}) |
|||
}, |
|||
handleRangeReset() { |
|||
if (!this.canReset) { |
|||
return; |
|||
} |
|||
this.gtValue = '' |
|||
this.ltValue = '' |
|||
this.handleRangeSubmit(true) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
.flex-r { |
|||
display: flex; |
|||
flex-direction: row; |
|||
} |
|||
|
|||
.flex-f { |
|||
flex: 1; |
|||
} |
|||
|
|||
.a-i-c { |
|||
align-items: center; |
|||
} |
|||
|
|||
.j-c-c { |
|||
justify-content: center; |
|||
} |
|||
|
|||
.icon-select { |
|||
width: 14px; |
|||
height: 16px; |
|||
border: solid 6px transparent; |
|||
border-top: solid 6px #ddd; |
|||
border-bottom: none; |
|||
background-color: #ddd; |
|||
background-clip: content-box; |
|||
box-sizing: border-box; |
|||
} |
|||
|
|||
.icon-select.active { |
|||
background-color: #1890ff; |
|||
border-top-color: #1890ff; |
|||
} |
|||
|
|||
.icon-search { |
|||
width: 12px; |
|||
height: 16px; |
|||
position: relative; |
|||
} |
|||
|
|||
.icon-search-0 { |
|||
border: 2px solid #ddd; |
|||
border-radius: 8px; |
|||
width: 7px; |
|||
height: 7px; |
|||
} |
|||
|
|||
.icon-search-1 { |
|||
position: absolute; |
|||
top: 8px; |
|||
right: 0; |
|||
width: 1px; |
|||
height: 7px; |
|||
background-color: #ddd; |
|||
transform: rotate(-45deg); |
|||
} |
|||
|
|||
.icon-search.active .icon-search-0 { |
|||
border-color: #1890ff; |
|||
} |
|||
|
|||
.icon-search.active .icon-search-1 { |
|||
background-color: #1890ff; |
|||
} |
|||
|
|||
.icon-calendar { |
|||
color: #ddd; |
|||
width: 14px; |
|||
height: 16px; |
|||
} |
|||
|
|||
.icon-calendar-0 { |
|||
height: 4px; |
|||
margin-top: 3px; |
|||
margin-bottom: 1px; |
|||
background-color: #ddd; |
|||
border-radius: 2px 2px 1px 1px; |
|||
position: relative; |
|||
} |
|||
.icon-calendar-0:before, .icon-calendar-0:after { |
|||
content: ''; |
|||
position: absolute; |
|||
top: -3px; |
|||
width: 4px; |
|||
height: 3px; |
|||
border-radius: 1px; |
|||
background-color: #ddd; |
|||
} |
|||
.icon-calendar-0:before { |
|||
left: 2px; |
|||
} |
|||
.icon-calendar-0:after { |
|||
right: 2px; |
|||
} |
|||
|
|||
.icon-calendar-1 { |
|||
height: 9px; |
|||
background-color: #ddd; |
|||
border-radius: 1px 1px 2px 2px; |
|||
} |
|||
|
|||
.icon-calendar.active { |
|||
color: #1890ff; |
|||
} |
|||
|
|||
.icon-calendar.active .icon-calendar-0, |
|||
.icon-calendar.active .icon-calendar-1, |
|||
.icon-calendar.active .icon-calendar-0:before, |
|||
.icon-calendar.active .icon-calendar-0:after { |
|||
background-color: #1890ff; |
|||
} |
|||
|
|||
.uni-filter-dropdown { |
|||
position: relative; |
|||
font-weight: normal; |
|||
} |
|||
|
|||
.dropdown-popup { |
|||
position: absolute; |
|||
top: 100%; |
|||
background-color: #fff; |
|||
box-shadow: 0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d; |
|||
min-width: 150px; |
|||
z-index: 1000; |
|||
} |
|||
|
|||
.dropdown-popup-left { |
|||
left: 0; |
|||
} |
|||
|
|||
.dropdown-popup-right { |
|||
right: 0; |
|||
} |
|||
|
|||
.uni-dropdown-cover { |
|||
position: fixed; |
|||
left: 0; |
|||
top: 0; |
|||
right: 0; |
|||
bottom: 0; |
|||
background-color: transparent; |
|||
z-index: 100; |
|||
} |
|||
|
|||
.list { |
|||
margin-top: 5px; |
|||
margin-bottom: 5px; |
|||
} |
|||
|
|||
.list-item { |
|||
padding: 5px 10px; |
|||
text-align: left; |
|||
} |
|||
|
|||
.list-item:hover { |
|||
background-color: #f0f0f0; |
|||
} |
|||
|
|||
.check { |
|||
margin-right: 5px; |
|||
} |
|||
|
|||
.search-area { |
|||
padding: 10px; |
|||
} |
|||
|
|||
.search-input { |
|||
font-size: 12px; |
|||
border: 1px solid #f0f0f0; |
|||
border-radius: 3px; |
|||
padding: 2px 5px; |
|||
min-width: 150px; |
|||
text-align: left; |
|||
} |
|||
|
|||
.input-label { |
|||
margin: 10px 10px 5px 10px; |
|||
text-align: left; |
|||
} |
|||
|
|||
.input { |
|||
font-size: 12px; |
|||
border: 1px solid #f0f0f0; |
|||
border-radius: 3px; |
|||
margin: 10px; |
|||
padding: 2px 5px; |
|||
min-width: 150px; |
|||
text-align: left; |
|||
} |
|||
|
|||
.opera-area { |
|||
cursor: default; |
|||
border-top: 1px solid #ddd; |
|||
padding: 5px; |
|||
} |
|||
|
|||
.opera-area .btn { |
|||
font-size: 12px; |
|||
border-radius: 3px; |
|||
margin: 5px; |
|||
padding: 4px 4px; |
|||
} |
|||
|
|||
.btn-default { |
|||
border: 1px solid #ddd; |
|||
} |
|||
|
|||
.btn-default.disable { |
|||
border-color: transparent; |
|||
} |
|||
|
|||
.btn-submit { |
|||
background-color: #1890ff; |
|||
color: #ffffff; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,251 @@ |
|||
<template> |
|||
<!-- #ifdef H5 --> |
|||
<th :rowspan="rowspan" :colspan="colspan" class="uni-table-th" :class="{ 'table--border': border }" :style="{ width: width + 'px', 'text-align': align }"> |
|||
<view class="uni-table-th-row"> |
|||
<view class="uni-table-th-content" :style="{ 'justify-content': contentAlign }" @click="sort"> |
|||
<slot></slot> |
|||
<view v-if="sortable" class="arrow-box"> |
|||
<text class="arrow up" :class="{ active: ascending }" @click.stop="ascendingFn"></text> |
|||
<text class="arrow down" :class="{ active: descending }" @click.stop="descendingFn"></text> |
|||
</view> |
|||
</view> |
|||
<dropdown v-if="filterType || filterData.length" :filterData="filterData" :filterType="filterType" @change="ondropdown"></dropdown> |
|||
</view> |
|||
</th> |
|||
<!-- #endif --> |
|||
<!-- #ifndef H5 --> |
|||
<view class="uni-table-th" :class="{ 'table--border': border }" :style="{ width: width + 'px', 'text-align': align }"><slot></slot></view> |
|||
<!-- #endif --> |
|||
</template> |
|||
|
|||
<script> |
|||
import dropdown from './filter-dropdown.vue' |
|||
/** |
|||
* Th 表头 |
|||
* @description 表格内的表头单元格组件 |
|||
* @tutorial https://ext.dcloud.net.cn/plugin?id=3270 |
|||
* @property {Number} width 单元格宽度 |
|||
* @property {Boolean} sortable 是否启用排序 |
|||
* @property {Number} align = [left|center|right] 单元格对齐方式 |
|||
* @value left 单元格文字左侧对齐 |
|||
* @value center 单元格文字居中 |
|||
* @value right 单元格文字右侧对齐 |
|||
* @property {Array} filterData 筛选数据 |
|||
* @property {String} filterType [search|select] 筛选类型 |
|||
* @value search 关键字搜素 |
|||
* @value select 条件选择 |
|||
* @event {Function} sort-change 排序触发事件 |
|||
*/ |
|||
export default { |
|||
name: 'uniTh', |
|||
options: { |
|||
virtualHost: true |
|||
}, |
|||
components: { |
|||
dropdown |
|||
}, |
|||
emits:['sort-change','filter-change'], |
|||
props: { |
|||
width: { |
|||
type: [String, Number], |
|||
default: '' |
|||
}, |
|||
align: { |
|||
type: String, |
|||
default: 'left' |
|||
}, |
|||
rowspan: { |
|||
type: [Number, String], |
|||
default: 1 |
|||
}, |
|||
colspan: { |
|||
type: [Number, String], |
|||
default: 1 |
|||
}, |
|||
sortable: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
filterType: { |
|||
type: String, |
|||
default: "" |
|||
}, |
|||
filterData: { |
|||
type: Array, |
|||
default () { |
|||
return [] |
|||
} |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
border: false, |
|||
ascending: false, |
|||
descending: false |
|||
} |
|||
}, |
|||
computed: { |
|||
contentAlign() { |
|||
let align = 'left' |
|||
switch (this.align) { |
|||
case 'left': |
|||
align = 'flex-start' |
|||
break |
|||
case 'center': |
|||
align = 'center' |
|||
break |
|||
case 'right': |
|||
align = 'flex-end' |
|||
break |
|||
} |
|||
return align |
|||
} |
|||
}, |
|||
created() { |
|||
this.root = this.getTable('uniTable') |
|||
this.rootTr = this.getTable('uniTr') |
|||
this.rootTr.minWidthUpdate(this.width ? this.width : 140) |
|||
this.border = this.root.border |
|||
this.root.thChildren.push(this) |
|||
}, |
|||
methods: { |
|||
sort() { |
|||
if (!this.sortable) return |
|||
this.clearOther() |
|||
if (!this.ascending && !this.descending) { |
|||
this.ascending = true |
|||
this.$emit('sort-change', { order: 'ascending' }) |
|||
return |
|||
} |
|||
if (this.ascending && !this.descending) { |
|||
this.ascending = false |
|||
this.descending = true |
|||
this.$emit('sort-change', { order: 'descending' }) |
|||
return |
|||
} |
|||
|
|||
if (!this.ascending && this.descending) { |
|||
this.ascending = false |
|||
this.descending = false |
|||
this.$emit('sort-change', { order: null }) |
|||
} |
|||
}, |
|||
ascendingFn() { |
|||
this.clearOther() |
|||
this.ascending = !this.ascending |
|||
this.descending = false |
|||
this.$emit('sort-change', { order: this.ascending ? 'ascending' : null }) |
|||
}, |
|||
descendingFn() { |
|||
this.clearOther() |
|||
this.descending = !this.descending |
|||
this.ascending = false |
|||
this.$emit('sort-change', { order: this.descending ? 'descending' : null }) |
|||
}, |
|||
clearOther() { |
|||
this.root.thChildren.map(item => { |
|||
if (item !== this) { |
|||
item.ascending = false |
|||
item.descending = false |
|||
} |
|||
return item |
|||
}) |
|||
}, |
|||
ondropdown(e) { |
|||
this.$emit("filter-change", e) |
|||
}, |
|||
/** |
|||
* 获取父元素实例 |
|||
*/ |
|||
getTable(name) { |
|||
let parent = this.$parent |
|||
let parentName = parent.$options.name |
|||
while (parentName !== name) { |
|||
parent = parent.$parent |
|||
if (!parent) return false |
|||
parentName = parent.$options.name |
|||
} |
|||
return parent |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
$border-color: #ebeef5; |
|||
|
|||
.uni-table-th { |
|||
padding: 12px 10px; |
|||
/* #ifndef APP-NVUE */ |
|||
display: table-cell; |
|||
box-sizing: border-box; |
|||
/* #endif */ |
|||
font-size: 14px; |
|||
font-weight: bold; |
|||
color: #909399; |
|||
border-bottom: 1px $border-color solid; |
|||
} |
|||
|
|||
.uni-table-th-row { |
|||
/* #ifndef APP-NVUE */ |
|||
display: flex; |
|||
/* #endif */ |
|||
flex-direction: row; |
|||
} |
|||
|
|||
.table--border { |
|||
border-right: 1px $border-color solid; |
|||
} |
|||
.uni-table-th-content { |
|||
display: flex; |
|||
align-items: center; |
|||
flex: 1; |
|||
} |
|||
.arrow-box { |
|||
} |
|||
.arrow { |
|||
display: block; |
|||
position: relative; |
|||
width: 10px; |
|||
height: 8px; |
|||
// border: 1px red solid; |
|||
left: 5px; |
|||
overflow: hidden; |
|||
cursor: pointer; |
|||
} |
|||
.down { |
|||
top: 3px; |
|||
::after { |
|||
content: ''; |
|||
width: 8px; |
|||
height: 8px; |
|||
position: absolute; |
|||
left: 2px; |
|||
top: -5px; |
|||
transform: rotate(45deg); |
|||
background-color: #ccc; |
|||
} |
|||
&.active { |
|||
::after { |
|||
background-color: #007aff; |
|||
} |
|||
} |
|||
} |
|||
.up { |
|||
::after { |
|||
content: ''; |
|||
width: 8px; |
|||
height: 8px; |
|||
position: absolute; |
|||
left: 2px; |
|||
top: 5px; |
|||
transform: rotate(45deg); |
|||
background-color: #ccc; |
|||
} |
|||
&.active { |
|||
::after { |
|||
background-color: #007aff; |
|||
} |
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,129 @@ |
|||
<template> |
|||
<!-- #ifdef H5 --> |
|||
<thead class="uni-table-thead"> |
|||
<tr class="uni-table-tr"> |
|||
<th :rowspan="rowspan" colspan="1" class="checkbox" :class="{ 'tr-table--border': border }"> |
|||
<table-checkbox :indeterminate="indeterminate" :checked="checked" @checkboxSelected="checkboxSelected"></table-checkbox> |
|||
</th> |
|||
</tr> |
|||
<slot></slot> |
|||
</thead> |
|||
<!-- #endif --> |
|||
<!-- #ifndef H5 --> |
|||
<view class="uni-table-thead"><slot></slot></view> |
|||
<!-- #endif --> |
|||
</template> |
|||
|
|||
<script> |
|||
import tableCheckbox from '../uni-tr/table-checkbox.vue' |
|||
export default { |
|||
name: 'uniThead', |
|||
components: { |
|||
tableCheckbox |
|||
}, |
|||
options: { |
|||
virtualHost: true |
|||
}, |
|||
data() { |
|||
return { |
|||
border: false, |
|||
selection: false, |
|||
rowspan: 1, |
|||
indeterminate: false, |
|||
checked: false |
|||
} |
|||
}, |
|||
created() { |
|||
this.root = this.getTable() |
|||
// #ifdef H5 |
|||
this.root.theadChildren = this |
|||
// #endif |
|||
this.border = this.root.border |
|||
this.selection = this.root.type |
|||
}, |
|||
methods: { |
|||
init(self) { |
|||
this.rowspan++ |
|||
}, |
|||
checkboxSelected(e) { |
|||
this.indeterminate = false |
|||
const backIndexData = this.root.backIndexData |
|||
const data = this.root.trChildren.filter(v => !v.disabled && v.keyValue) |
|||
if (backIndexData.length === data.length) { |
|||
this.checked = false |
|||
this.root.clearSelection() |
|||
} else { |
|||
this.checked = true |
|||
this.root.selectionAll() |
|||
} |
|||
}, |
|||
/** |
|||
* 获取父元素实例 |
|||
*/ |
|||
getTable(name = 'uniTable') { |
|||
let parent = this.$parent |
|||
let parentName = parent.$options.name |
|||
while (parentName !== name) { |
|||
parent = parent.$parent |
|||
if (!parent) return false |
|||
parentName = parent.$options.name |
|||
} |
|||
return parent |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
$border-color: #ebeef5; |
|||
|
|||
.uni-table-thead { |
|||
display: table-header-group; |
|||
} |
|||
|
|||
.uni-table-tr { |
|||
/* #ifndef APP-NVUE */ |
|||
display: table-row; |
|||
transition: all 0.3s; |
|||
box-sizing: border-box; |
|||
/* #endif */ |
|||
border: 1px red solid; |
|||
background-color: #fafafa; |
|||
} |
|||
|
|||
.checkbox { |
|||
padding: 0 8px; |
|||
width: 26px; |
|||
padding-left: 12px; |
|||
/* #ifndef APP-NVUE */ |
|||
display: table-cell; |
|||
vertical-align: middle; |
|||
/* #endif */ |
|||
color: #333; |
|||
font-weight: 500; |
|||
border-bottom: 1px $border-color solid; |
|||
font-size: 14px; |
|||
// text-align: center; |
|||
} |
|||
|
|||
.tr-table--border { |
|||
border-right: 1px $border-color solid; |
|||
} |
|||
|
|||
/* #ifndef APP-NVUE */ |
|||
.uni-table-tr { |
|||
::v-deep .uni-table-th { |
|||
&.table--border:last-child { |
|||
// border-right: none; |
|||
} |
|||
} |
|||
|
|||
::v-deep .uni-table-td { |
|||
&.table--border:last-child { |
|||
// border-right: none; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/* #endif */ |
|||
</style> |
|||
@ -0,0 +1,179 @@ |
|||
<template> |
|||
<view class="uni-table-checkbox" @click="selected"> |
|||
<view v-if="!indeterminate" class="checkbox__inner" :class="{'is-checked':isChecked,'is-disable':isDisabled}"> |
|||
<view class="checkbox__inner-icon"></view> |
|||
</view> |
|||
<view v-else class="checkbox__inner checkbox--indeterminate"> |
|||
<view class="checkbox__inner-icon"></view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
export default { |
|||
name: 'TableCheckbox', |
|||
emits:['checkboxSelected'], |
|||
props: { |
|||
indeterminate: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
checked: { |
|||
type: [Boolean,String], |
|||
default: false |
|||
}, |
|||
disabled: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
index: { |
|||
type: Number, |
|||
default: -1 |
|||
}, |
|||
cellData: { |
|||
type: Object, |
|||
default () { |
|||
return {} |
|||
} |
|||
} |
|||
}, |
|||
watch:{ |
|||
checked(newVal){ |
|||
if(typeof this.checked === 'boolean'){ |
|||
this.isChecked = newVal |
|||
}else{ |
|||
this.isChecked = true |
|||
} |
|||
}, |
|||
indeterminate(newVal){ |
|||
this.isIndeterminate = newVal |
|||
} |
|||
}, |
|||
data() { |
|||
return { |
|||
isChecked: false, |
|||
isDisabled: false, |
|||
isIndeterminate:false |
|||
} |
|||
}, |
|||
created() { |
|||
if(typeof this.checked === 'boolean'){ |
|||
this.isChecked = this.checked |
|||
} |
|||
this.isDisabled = this.disabled |
|||
}, |
|||
methods: { |
|||
selected() { |
|||
if (this.isDisabled) return |
|||
this.isIndeterminate = false |
|||
this.isChecked = !this.isChecked |
|||
this.$emit('checkboxSelected', { |
|||
checked: this.isChecked, |
|||
data: this.cellData |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
$checked-color: #007aff; |
|||
$border-color: #DCDFE6; |
|||
$disable:0.4; |
|||
|
|||
.uni-table-checkbox { |
|||
display: flex; |
|||
flex-direction: row; |
|||
align-items: center; |
|||
justify-content: center; |
|||
position: relative; |
|||
margin: 5px 0; |
|||
cursor: pointer; |
|||
|
|||
// 多选样式 |
|||
.checkbox__inner { |
|||
/* #ifndef APP-NVUE */ |
|||
flex-shrink: 0; |
|||
box-sizing: border-box; |
|||
/* #endif */ |
|||
position: relative; |
|||
width: 16px; |
|||
height: 16px; |
|||
border: 1px solid $border-color; |
|||
border-radius: 2px; |
|||
background-color: #fff; |
|||
z-index: 1; |
|||
|
|||
.checkbox__inner-icon { |
|||
position: absolute; |
|||
/* #ifdef APP-NVUE */ |
|||
top: 2px; |
|||
/* #endif */ |
|||
/* #ifndef APP-NVUE */ |
|||
top: 2px; |
|||
/* #endif */ |
|||
left: 5px; |
|||
height: 7px; |
|||
width: 3px; |
|||
border: 1px solid #fff; |
|||
border-left: 0; |
|||
border-top: 0; |
|||
opacity: 0; |
|||
transform-origin: center; |
|||
transform: rotate(45deg); |
|||
box-sizing: content-box; |
|||
} |
|||
|
|||
&.checkbox--indeterminate { |
|||
border-color: $checked-color; |
|||
background-color: $checked-color; |
|||
|
|||
.checkbox__inner-icon { |
|||
position: absolute; |
|||
opacity: 1; |
|||
transform: rotate(0deg); |
|||
height: 2px; |
|||
top: 0; |
|||
bottom: 0; |
|||
margin: auto; |
|||
left: 0px; |
|||
right: 0px; |
|||
bottom: 0; |
|||
width: auto; |
|||
border: none; |
|||
border-radius: 2px; |
|||
transform: scale(0.5); |
|||
background-color: #fff; |
|||
} |
|||
} |
|||
&:hover{ |
|||
border-color: $checked-color; |
|||
} |
|||
// 禁用 |
|||
&.is-disable { |
|||
/* #ifdef H5 */ |
|||
cursor: not-allowed; |
|||
/* #endif */ |
|||
background-color: #F2F6FC; |
|||
border-color: $border-color; |
|||
} |
|||
|
|||
// 选中 |
|||
&.is-checked { |
|||
border-color: $checked-color; |
|||
background-color: $checked-color; |
|||
|
|||
.checkbox__inner-icon { |
|||
opacity: 1; |
|||
transform: rotate(45deg); |
|||
} |
|||
|
|||
// 选中禁用 |
|||
&.is-disable { |
|||
opacity: $disable; |
|||
} |
|||
} |
|||
|
|||
} |
|||
} |
|||
</style> |
|||
@ -0,0 +1,171 @@ |
|||
<template> |
|||
<!-- #ifdef H5 --> |
|||
<tr class="uni-table-tr"> |
|||
<th v-if="selection === 'selection' && ishead" class="checkbox" :class="{ 'tr-table--border': border }"> |
|||
<table-checkbox :checked="checked" :indeterminate="indeterminate" :disabled="disabled" @checkboxSelected="checkboxSelected"></table-checkbox> |
|||
</th> |
|||
<slot></slot> |
|||
<!-- <uni-th class="th-fixed">123</uni-th> --> |
|||
</tr> |
|||
<!-- #endif --> |
|||
<!-- #ifndef H5 --> |
|||
<view class="uni-table-tr"> |
|||
<view v-if="selection === 'selection' " class="checkbox" :class="{ 'tr-table--border': border }"> |
|||
<table-checkbox :checked="checked" :indeterminate="indeterminate" :disabled="disabled" @checkboxSelected="checkboxSelected"></table-checkbox> |
|||
</view> |
|||
<slot></slot> |
|||
</view> |
|||
<!-- #endif --> |
|||
</template> |
|||
|
|||
<script> |
|||
import tableCheckbox from './table-checkbox.vue' |
|||
/** |
|||
* Tr 表格行组件 |
|||
* @description 表格行组件 仅包含 th,td 组件 |
|||
* @tutorial https://ext.dcloud.net.cn/plugin?id= |
|||
*/ |
|||
export default { |
|||
name: 'uniTr', |
|||
components: { tableCheckbox }, |
|||
props: { |
|||
disabled: { |
|||
type: Boolean, |
|||
default: false |
|||
}, |
|||
keyValue: { |
|||
type: [String, Number], |
|||
default: '' |
|||
} |
|||
}, |
|||
options: { |
|||
virtualHost: true |
|||
}, |
|||
data() { |
|||
return { |
|||
value: false, |
|||
border: false, |
|||
selection: false, |
|||
widthThArr: [], |
|||
ishead: true, |
|||
checked: false, |
|||
indeterminate:false |
|||
} |
|||
}, |
|||
created() { |
|||
this.root = this.getTable() |
|||
this.head = this.getTable('uniThead') |
|||
if (this.head) { |
|||
this.ishead = false |
|||
this.head.init(this) |
|||
} |
|||
this.border = this.root.border |
|||
this.selection = this.root.type |
|||
this.root.trChildren.push(this) |
|||
const rowData = this.root.data.find(v => v[this.root.rowKey] === this.keyValue) |
|||
if(rowData){ |
|||
this.rowData = rowData |
|||
} |
|||
this.root.isNodata() |
|||
}, |
|||
mounted() { |
|||
if (this.widthThArr.length > 0) { |
|||
const selectionWidth = this.selection === 'selection' ? 50 : 0 |
|||
this.root.minWidth = this.widthThArr.reduce((a, b) => Number(a) + Number(b)) + selectionWidth |
|||
} |
|||
}, |
|||
// #ifndef VUE3 |
|||
destroyed() { |
|||
const index = this.root.trChildren.findIndex(i => i === this) |
|||
this.root.trChildren.splice(index, 1) |
|||
this.root.isNodata() |
|||
}, |
|||
// #endif |
|||
// #ifdef VUE3 |
|||
unmounted() { |
|||
const index = this.root.trChildren.findIndex(i => i === this) |
|||
this.root.trChildren.splice(index, 1) |
|||
this.root.isNodata() |
|||
}, |
|||
// #endif |
|||
methods: { |
|||
minWidthUpdate(width) { |
|||
this.widthThArr.push(width) |
|||
}, |
|||
// 选中 |
|||
checkboxSelected(e) { |
|||
let rootData = this.root.data.find(v => v[this.root.rowKey] === this.keyValue) |
|||
this.checked = e.checked |
|||
this.root.check(rootData||this, e.checked,rootData? this.keyValue:null) |
|||
}, |
|||
change(e) { |
|||
this.root.trChildren.forEach(item => { |
|||
if (item === this) { |
|||
this.root.check(this, e.detail.value.length > 0 ? true : false) |
|||
} |
|||
}) |
|||
}, |
|||
/** |
|||
* 获取父元素实例 |
|||
*/ |
|||
getTable(name = 'uniTable') { |
|||
let parent = this.$parent |
|||
let parentName = parent.$options.name |
|||
while (parentName !== name) { |
|||
parent = parent.$parent |
|||
if (!parent) return false |
|||
parentName = parent.$options.name |
|||
} |
|||
return parent |
|||
} |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<style lang="scss"> |
|||
$border-color: #ebeef5; |
|||
|
|||
.uni-table-tr { |
|||
/* #ifndef APP-NVUE */ |
|||
display: table-row; |
|||
transition: all 0.3s; |
|||
box-sizing: border-box; |
|||
/* #endif */ |
|||
} |
|||
|
|||
.checkbox { |
|||
padding: 0 8px; |
|||
width: 26px; |
|||
padding-left: 12px; |
|||
/* #ifndef APP-NVUE */ |
|||
display: table-cell; |
|||
vertical-align: middle; |
|||
/* #endif */ |
|||
color: #333; |
|||
font-weight: 500; |
|||
border-bottom: 1px $border-color solid; |
|||
font-size: 14px; |
|||
// text-align: center; |
|||
} |
|||
|
|||
.tr-table--border { |
|||
border-right: 1px $border-color solid; |
|||
} |
|||
|
|||
/* #ifndef APP-NVUE */ |
|||
.uni-table-tr { |
|||
::v-deep .uni-table-th { |
|||
&.table--border:last-child { |
|||
// border-right: none; |
|||
} |
|||
} |
|||
|
|||
::v-deep .uni-table-td { |
|||
&.table--border:last-child { |
|||
// border-right: none; |
|||
} |
|||
} |
|||
} |
|||
|
|||
/* #endif */ |
|||
</style> |
|||
@ -0,0 +1,86 @@ |
|||
{ |
|||
"id": "uni-table", |
|||
"displayName": "uni-table 表格", |
|||
"version": "1.2.0", |
|||
"description": "表格组件,多用于展示多条结构类似的数据,如", |
|||
"keywords": [ |
|||
"uni-ui", |
|||
"uniui", |
|||
"table", |
|||
"表格" |
|||
], |
|||
"repository": "https://github.com/dcloudio/uni-ui", |
|||
"engines": { |
|||
"HBuilderX": "" |
|||
}, |
|||
"directories": { |
|||
"example": "../../temps/example_temps" |
|||
}, |
|||
"dcloudext": { |
|||
"category": [ |
|||
"前端组件", |
|||
"通用组件" |
|||
], |
|||
"sale": { |
|||
"regular": { |
|||
"price": "0.00" |
|||
}, |
|||
"sourcecode": { |
|||
"price": "0.00" |
|||
} |
|||
}, |
|||
"contact": { |
|||
"qq": "" |
|||
}, |
|||
"declaration": { |
|||
"ads": "无", |
|||
"data": "无", |
|||
"permissions": "无" |
|||
}, |
|||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" |
|||
}, |
|||
"uni_modules": { |
|||
"dependencies": ["uni-scss","uni-datetime-picker"], |
|||
"encrypt": [], |
|||
"platforms": { |
|||
"cloud": { |
|||
"tcb": "y", |
|||
"aliyun": "y" |
|||
}, |
|||
"client": { |
|||
"App": { |
|||
"app-vue": "y", |
|||
"app-nvue": "n" |
|||
}, |
|||
"H5-mobile": { |
|||
"Safari": "y", |
|||
"Android Browser": "y", |
|||
"微信浏览器(Android)": "y", |
|||
"QQ浏览器(Android)": "y" |
|||
}, |
|||
"H5-pc": { |
|||
"Chrome": "y", |
|||
"IE": "y", |
|||
"Edge": "y", |
|||
"Firefox": "y", |
|||
"Safari": "y" |
|||
}, |
|||
"小程序": { |
|||
"微信": "y", |
|||
"阿里": "y", |
|||
"百度": "y", |
|||
"字节跳动": "n", |
|||
"QQ": "y" |
|||
}, |
|||
"快应用": { |
|||
"华为": "n", |
|||
"联盟": "n" |
|||
}, |
|||
"Vue": { |
|||
"vue2": "y", |
|||
"vue3": "y" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,13 @@ |
|||
|
|||
|
|||
## Table 表单 |
|||
> 组件名:``uni-table``,代码块: `uTable`。 |
|||
|
|||
用于展示多条结构类似的数据 |
|||
|
|||
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-table) |
|||
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 |
|||
|
|||
|
|||
|
|||
|
|||
Write
Preview
Loading…
Cancel
Save