feat(date-picker): [date-picker] date picker add quarter type (#1513)

* chore: add comments to date-panel

* feat(date-picker): add quarter-panel component and support type="quarter"

* feat(date-picker): quarter-panel support prevYear/nextYear/pickYear

* refactor(date-picker): remove QuarterMap to common

* feat(date-picker): optimize quarter-panel style

* refactor(date-picker): move MonthQuarterMap to common

* chore: add QuarterPanel on modules.json

* docs(date-picker): add quarter to IType

* fix(date-picker): [date-picker] fix e2e test failed

* chore: add node version
This commit is contained in:
Kagol 2024-03-30 09:59:25 +08:00 committed by GitHub
parent fedf1cb1fe
commit 8de365c231
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 351 additions and 13 deletions

View File

@ -624,7 +624,7 @@ interface IPickerOptions {
name: 'IType',
type: 'type',
code: `
type IType = 'date' | 'dates' | 'daterange' | 'datetime' | 'datetimerange' | 'week' | 'month' | 'monthrange' | 'year' | 'years' | 'yearrange'
type IType = 'date' | 'dates' | 'daterange' | 'datetime' | 'datetimerange' | 'week' | 'month' | 'monthrange' | 'quarter' | 'year' | 'years' | 'yearrange'
`
}
]

View File

@ -4,6 +4,7 @@
<tiny-date-picker v-model="dateTimeValue" type="datetime" placeholder="请选择日期"></tiny-date-picker>
<tiny-date-picker v-model="weekValue" type="week" placeholder="请选择周"></tiny-date-picker>
<tiny-date-picker v-model="monthValue" type="month" placeholder="请选择月份"></tiny-date-picker>
<tiny-date-picker v-model="quarterValue" type="quarter" placeholder="请选择季度"></tiny-date-picker>
<tiny-date-picker v-model="yearValue" type="year" placeholder="请选择年份"></tiny-date-picker>
</div>
</template>
@ -16,6 +17,7 @@ const value = ref('')
const dateTimeValue = ref('')
const weekValue = ref('')
const monthValue = ref('')
const quarterValue = ref('')
const yearValue = ref('')
</script>

View File

@ -4,6 +4,7 @@
<tiny-date-picker v-model="dateTimeValue" type="datetime" placeholder="请选择日期"></tiny-date-picker>
<tiny-date-picker v-model="weekValue" type="week" placeholder="请选择周"></tiny-date-picker>
<tiny-date-picker v-model="monthValue" type="month" placeholder="请选择月份"></tiny-date-picker>
<tiny-date-picker v-model="quarterValue" type="quarter" placeholder="请选择季度"></tiny-date-picker>
<tiny-date-picker v-model="yearValue" type="year" placeholder="请选择年份"></tiny-date-picker>
</div>
</template>
@ -21,6 +22,7 @@ export default {
dateTimeValue: '',
weekValue: '',
monthValue: '',
quarterValue: '',
yearValue: ''
}
}

View File

@ -15,11 +15,11 @@ test('[DatePicker] 测试选择器打开时默认时间设置', async ({ page })
await dateInputDefaultTime.press('Enter')
await page.getByRole('textbox', { name: '2023-05-20 09:00:00' }).click()
await page.getByRole('cell', { name: '15' }).getByText('15').click()
await page.getByRole('cell', { name: '15' }).getByText('15').last().click()
await expect(page.getByRole('textbox', { name: '选择时间' })).toHaveValue('09:00:00')
await page.getByRole('textbox').nth(3).click()
await page.getByRole('cell', { name: '10' }).getByText('10').first().click()
await page.getByRole('cell', { name: '10' }).getByText('10').last().click()
await page.getByRole('cell', { name: '10' }).getByText('10').nth(1).click()
await expect(page.getByRole('textbox', { name: '开始时间' })).toHaveValue('09:00:00')
await expect(page.getByRole('textbox', { name: '结束时间' })).toHaveValue('18:00:00')

View File

@ -19,7 +19,7 @@ test('[DatePicker] 测试事件', async ({ page }) => {
.filter({ hasText: /^blur:$/ })
.getByRole('textbox')
.click()
await page.getByRole('cell', { name: '15' }).getByText('15').click()
await page.getByRole('cell', { name: '15' }).getByText('15').last().click()
const blurEventMessageDom = page.locator('div').filter({ hasText: '触发 blur 事件' }).nth(1)
await expect(blurEventMessageDom).toBeVisible()
@ -33,13 +33,13 @@ test('[DatePicker] 测试事件', async ({ page }) => {
.filter({ hasText: /^change:$/ })
.getByRole('textbox')
.click()
await page.getByRole('cell', { name: '15' }).getByText('15').click()
await page.getByRole('cell', { name: '15' }).getByText('15').last().click()
const changeEventMessageDom = page.locator('div').filter({ hasText: '触发 change 事件,组件绑定值为:' }).nth(1)
await expect(changeEventMessageDom).toBeVisible()
// onPick 事件
await page.locator('div').filter({ hasText: /^-$/ }).click()
await page.getByRole('cell', { name: '10' }).getByText('10').first().click()
await page.getByRole('cell', { name: '10' }).getByText('10').last().click()
const onPickEventMessageDom = page.locator('div').filter({ hasText: '触发 onPick 事件,开始日期为:' }).nth(1)
await expect(onPickEventMessageDom).toBeVisible()
})

View File

@ -6,7 +6,7 @@ test('[DatePicker] 测试日期格式化', async ({ page }) => {
// format: 日期输入框中显示的格式
await page.getByRole('textbox', { name: '2023 年 05 月 24 日' }).first().click()
await page.getByRole('cell', { name: '20' }).getByText('20').click()
await page.getByRole('cell', { name: '20' }).getByText('20').last().click()
await expect(page.getByRole('textbox', { name: '2023 年 05 月 20 日' }).first()).toBeVisible()
// time-format: 时间输入框中显示的格式
@ -17,6 +17,6 @@ test('[DatePicker] 测试日期格式化', async ({ page }) => {
// value-format: 选中值的格式
await page.locator('.tiny-date-editor input').nth(2).click()
await page.getByRole('cell', { name: '20' }).getByText('20').click()
await page.getByRole('cell', { name: '20' }).getByText('20').last().click()
await expect(page.locator('.select-date')).toHaveText(`当前选中时间:${1589904000000}`)
})

View File

@ -31,7 +31,7 @@
"pnpm": ">=6.35"
},
"scripts": {
"preinstall": "npx only-allow pnpm",
"preinstall": "node -v && pnpm -v && npx only-allow pnpm",
"postinstall": "pnpm build:internals",
"prepare": "husky install",
"bootstrap": "pnpm --filter=\"!./packages/dist/**\" install",

View File

@ -1083,6 +1083,11 @@
"type": "template",
"exclude": false
},
"QuarterPanel": {
"path": "vue/src/quarter-panel/src/pc.vue",
"type": "template",
"exclude": false
},
"Dept": {
"path": "vue/src/dept/index.ts",
"type": "component",

View File

@ -128,7 +128,7 @@ export const DATE = {
}
const TriggerTypes =
'date,datetime,time,time-select,week,month,year,years,yearrange,daterange,monthrange,timerange,datetimerange,dates'
'date,datetime,time,time-select,week,month,year,years,yearrange,daterange,monthrange,timerange,datetimerange,dates,quarter'
export const DATEPICKER = {
Day: 'day',
@ -167,6 +167,18 @@ export const DATEPICKER = {
center: 'bottom',
right: 'bottom-end'
},
QuarterMap: {
0: 0,
1: 3,
2: 6,
3: 9
},
MonthQuarterMap: {
0: 1,
3: 2,
6: 3,
9: 4
},
TriggerTypes: TriggerTypes.split(','),
DateFormats: {
year: 'yyyy',
@ -184,6 +196,7 @@ export const DATEPICKER = {
},
Time: 'time',
TimeRange: 'timerange',
Quarter: 'quarter',
IconTime: 'icon-time',
IconDate: 'icon-Calendar',
DateRange: 'daterange',

View File

@ -21,7 +21,16 @@ import globalTimezone from './timezone'
const iso8601Reg = /^\d{4}-\d{2}-\d{2}(.)\d{2}:\d{2}:\d{2}(.+)$/
export const getPanel =
({ DatePanel, DateRangePanel, MonthRangePanel, YearRangePanel, TimePanel, TimeRangePanel, TimeSelect }) =>
({
DatePanel,
DateRangePanel,
MonthRangePanel,
YearRangePanel,
TimePanel,
TimeRangePanel,
QuarterPanel,
TimeSelect
}) =>
(type) => {
if (type === DATEPICKER.DateRange || type === DATEPICKER.DateTimeRange) {
return DateRangePanel
@ -35,6 +44,8 @@ export const getPanel =
return TimePanel
} else if (type === DATEPICKER.TimeSelect) {
return TimeSelect
} else if (type === DATEPICKER.Quarter) {
return QuarterPanel
}
return DatePanel
@ -413,7 +424,11 @@ export const typeValueResolveMap =
years: getDatesOfTypeValueResolveMap(api),
yearrange: getDatesOfTypeValueResolveMap(api),
number: getNumberOfTypeValueResolveMap(),
dates: getDatesOfTypeValueResolveMap(api)
dates: getDatesOfTypeValueResolveMap(api),
quarter: {
formatter: (value) => `${value.getFullYear()}-Q${DATEPICKER.MonthQuarterMap[value.getMonth()]}`,
parser: api.dateParser
}
})
export const firstInputId =

View File

@ -0,0 +1,81 @@
import { modifyDate, nextYear, prevYear } from '../common/deps/date-util'
import { DATEPICKER } from '../common'
const getTarget = (event) => {
let target = event.target
const tagName = target.tagName
if (tagName === 'A') {
target = target.parentNode.parentNode
}
if (tagName === 'DIV') {
target = target.parentNode
}
if (target.tagName !== 'TD') {
return
}
return target
}
export const handleQuarterTableClick =
({ state, emit }) =>
(event) => {
const target = getTarget(event)
const currentDate = new Date(state.date.getFullYear(), DATEPICKER.QuarterMap[target.cellIndex], 1)
state.value = currentDate
emit('pick', currentDate)
}
export const showYearPicker =
({ state }) =>
() =>
(state.currentView = DATEPICKER.Year)
export const cusPrevYear =
({ state }) =>
() => {
if (state.currentView === DATEPICKER.Year) {
state.startYear = state.startYear - DATEPICKER.PanelYearNum
} else {
state.date = prevYear(state.date)
}
}
export const cusNextYear =
({ state }) =>
() => {
if (state.currentView === DATEPICKER.Year) {
state.startYear = state.startYear + DATEPICKER.PanelYearNum
} else {
state.date = nextYear(state.date)
}
}
export const handleYearPick =
({ state }) =>
(value) => {
state.currentView = DATEPICKER.Quarter
state.date = modifyDate(state.date, value, state.date.getMonth(), state.date.getDate())
}
export const getYearLabel =
({ state, t }) =>
() => {
return state.date.getFullYear()
}
export const getCellStyle =
({ api, props, state }) =>
(cell) => {
const year = state.date.getFullYear()
const quarter = cell.text.slice(1) - 1
const style = {}
style.current =
state.value && state.value.getFullYear() === year && state.value.getMonth() === DATEPICKER.QuarterMap[quarter]
return style
}

View File

@ -0,0 +1,46 @@
import { DATEPICKER } from '../common'
import {
handleQuarterTableClick,
showYearPicker,
handleYearPick,
cusPrevYear,
cusNextYear,
getYearLabel,
getCellStyle
} from './index'
export const api = [
'state',
'handleQuarterTableClick',
'showYearPicker',
'handleYearPick',
'cusPrevYear',
'cusNextYear',
'getCellStyle'
]
export const renderless = (props, { reactive, computed }, { emit, t }) => {
const api = {}
const state = reactive({
date: new Date(),
visible: false,
currentView: DATEPICKER.Quarter,
yearLabel: computed(() => api.getYearLabel()),
startYear: Math.floor(new Date().getFullYear() / 10) * 10,
rows: [{ text: 'Q1' }, { text: 'Q2' }, { text: 'Q3' }, { text: 'Q4' }]
})
Object.assign(api, {
state,
handleQuarterTableClick: handleQuarterTableClick({ state, emit }),
showYearPicker: showYearPicker({ state }),
handleYearPick: handleYearPick({ state }),
cusPrevYear: cusPrevYear({ state }),
cusNextYear: cusNextYear({ state }),
getYearLabel: getYearLabel({ state, t }),
getCellStyle: getCellStyle({ api, props, state })
})
return api
}

View File

@ -0,0 +1,35 @@
@import '../custom.less';
@import './vars.less';
@import '../month-table/index.less';
@import '../month-table/vars.less';
@import '../date-table/vars.less';
@quarter-panel-prefix-cls: ~'@{css-prefix}quarter-panel';
.@{quarter-panel-prefix-cls} {
.component-css-vars-quarter-panel();
table {
table-layout: fixed;
width: 100%;
}
&__header {
margin: 12px 12px 0 12px;
text-align: center;
padding-bottom: 12px;
border-bottom: 1px solid var(--ti-date-picker-border-color);
line-height: var(--ti-date-picker-header-line-height);
}
&__content {
}
&__table {
.component-css-vars-month-table();
.component-css-vars-date-table();
.month-table();
}
}

View File

@ -0,0 +1 @@
.component-css-vars-quarter-panel() {}

View File

@ -46,7 +46,7 @@ export default {
'ip-address': 'ip-address',
'link-menu': 'link-menu',
'month-range': 'month-range',
'month-table': 'month-table,year-table',
'month-table': 'month-table,year-table,quarter-panel__table',
'nav-menu': 'nav-menu',
'option-group': 'option-group',
'popup-horiz-menu': 'popup-horiz-menu',

View File

@ -24,6 +24,8 @@
>
<div class="tiny-picker-panel__body-wrapper">
<slot name="sidebar" class="tiny-picker-panel__sidebar"></slot>
<!-- 快捷选项 -->
<div class="tiny-picker-panel__sidebar" v-if="state.shortcuts">
<button
type="button"
@ -36,7 +38,9 @@
{{ shortcut.text }}
</button>
</div>
<div class="tiny-picker-panel__body">
<!-- 时间选择输入框 -->
<div class="tiny-date-picker__time-header" v-if="state.showTime">
<span class="tiny-date-picker__editor-wrap">
<tiny-input
@ -70,6 +74,8 @@
</time-picker>
</span>
</div>
<!-- 上一年/上一月/下一月/下一年 -->
<div
class="tiny-date-picker__header"
:class="{
@ -124,6 +130,7 @@
</button>
</div>
<!-- 日期表格 -->
<div class="tiny-picker-panel__content">
<date-table
ref="dateTable"
@ -166,6 +173,7 @@
</div>
</div>
<!-- 时区选择 -->
<div
class="tiny-picker-panel__timezone"
v-if="state.isShowTz || state.timezone.isServiceTimezone"
@ -201,6 +209,7 @@
</div>
</div>
<!-- 此刻/确认 -->
<div class="tiny-picker-panel__footer" v-show="state.isShowFooter">
<tiny-button
size="mini"

View File

@ -22,6 +22,7 @@
"@opentiny/vue-date-panel": "workspace:~",
"@opentiny/vue-date-range": "workspace:~",
"@opentiny/vue-month-range": "workspace:~",
"@opentiny/vue-quarter-panel": "workspace:~",
"@opentiny/vue-time": "workspace:~",
"@opentiny/vue-time-range": "workspace:~",
"@opentiny/vue-time-panel": "workspace:~",

View File

@ -162,6 +162,7 @@ import MonthRangePanel from '@opentiny/vue-month-range'
import YearRangePanel from '@opentiny/vue-year-range'
import TimePanel from '@opentiny/vue-time'
import TimeRangePanel from '@opentiny/vue-time-range'
import QuarterPanel from '@opentiny/vue-quarter-panel'
import TimeSelect from '@opentiny/vue-time-panel'
import TinyTooltip from '@opentiny/vue-tooltip'
import FilterBox from '@opentiny/vue-filter-box'
@ -193,6 +194,7 @@ export default defineComponent({
YearRangePanel,
TimePanel,
TimeRangePanel,
QuarterPanel,
TimeSelect
}
})

View File

@ -0,0 +1,29 @@
/**
* Copyright (c) 2022 - present TinyVue Authors.
* Copyright (c) 2022 - present Huawei Cloud Computing Technologies Co., Ltd.
*
* Use of this source code is governed by an MIT-style license.
*
* THE OPEN SOURCE SOFTWARE IN THIS PRODUCT IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL,
* BUT WITHOUT ANY WARRANTY, WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import QuerterPanel from './src/pc.vue'
import { version } from './package.json'
/* istanbul ignore next */
QuerterPanel.install = function (Vue) {
Vue.component(QuerterPanel.name, QuerterPanel)
}
QuerterPanel.version = version
/* istanbul ignore next */
if (process.env.BUILD_TARGET === 'runtime') {
if (typeof window !== 'undefined' && window.Vue) {
QuerterPanel.install(window.Vue)
}
}
export default QuerterPanel

View File

@ -0,0 +1,23 @@
{
"name": "@opentiny/vue-quarter-panel",
"version": "3.7.0",
"description": "",
"main": "lib/index.js",
"module": "index.ts",
"sideEffects": false,
"type": "module",
"devDependencies": {
"@opentiny-internal/vue-test-utils": "workspace:*",
"vitest": "^0.31.0"
},
"scripts": {
"build": "pnpm -w build:ui $npm_package_name",
"//postversion": "pnpm build"
},
"dependencies": {
"@opentiny/vue-common": "workspace:~",
"@opentiny/vue-icon": "workspace:~",
"@opentiny/vue-renderless": "workspace:~"
},
"license": "MIT"
}

View File

@ -0,0 +1,74 @@
<template>
<transition name="tiny-zoom-in-top">
<div v-show="state.visible" class="tiny-quarter-panel tiny-picker-panel tiny-date-picker">
<!-- 上一年/下一年 -->
<div class="tiny-quarter-panel__header">
<button
type="button"
@click="cusPrevYear"
class="tiny-picker-panel__icon-btn tiny-date-picker__prev-btn tiny-icon-d-arrow-left"
>
<icon-double-left></icon-double-left>
</button>
<span @click="showYearPicker" role="button" class="tiny-date-picker__header-label">{{ state.yearLabel }}</span>
<button
type="button"
@click="cusNextYear"
class="tiny-picker-panel__icon-btn tiny-date-picker__next-btn tiny-icon-d-arrow-right"
>
<icon-double-right></icon-double-right>
</button>
</div>
<div class="tiny-quarter-panel__content tiny-picker-panel__content">
<table
class="tiny-quarter-panel__table"
v-if="state.currentView === 'quarter'"
@click="handleQuarterTableClick"
>
<tbody>
<tr>
<td v-for="(row, key) in state.rows" :key="key" :class="getCellStyle(row)">
<div>
<a class="cell" v-text="row.text"></a>
</div>
</td>
</tr>
</tbody>
</table>
<year-table
ref="yearTable"
v-if="state.currentView === 'year'"
:value="state.value"
:default-value="state.defaultValue ? new Date(state.defaultValue) : null"
:date="state.date"
:disabled-date="state.disabledDate"
:selection-mode="state.selectionMode"
:start-year="state.startYear"
@pick="handleYearPick"
>
</year-table>
</div>
</div>
</transition>
</template>
<script lang="ts">
import { renderless, api } from '@opentiny/vue-renderless/quarter-panel/vue'
import { $prefix, setup, defineComponent } from '@opentiny/vue-common'
import YearTable from '@opentiny/vue-year-table'
import { iconDoubleRight, iconDoubleLeft } from '@opentiny/vue-icon'
import '@opentiny/vue-theme/quarter-panel/index.less'
export default defineComponent({
name: $prefix + 'QuarterPanel',
components: {
YearTable,
IconDoubleRight: iconDoubleRight(),
IconDoubleLeft: iconDoubleLeft()
},
setup(props, context) {
return setup({ props, context, renderless, api })
}
})
</script>