Commit 6f2cf656 authored by “yiyousong”'s avatar “yiyousong”

perf: 优化新增菜单

parent 3918c5c4
......@@ -26,7 +26,7 @@
</template>
<script>
import iconJson from '@/assets/icon.json';
import iconJson from './icon.json';
export default {
model: {
prop: 'value',
......
<template>
<el-popover ref="popover" trigger="click" :placement="placement" popper-class="y-date-popover">
<div class="flex flex-col items-center">
<div class="tab-box">
<div :class="['tab-item', { active: type == 'year' }]" @click="changeTab('year')">按年</div>
<div :class="['tab-item', { active: type == 'month' }]" @click="changeTab('month')">
按月
</div>
<div :class="['tab-item', { active: type == 'date' }]" @click="changeTab('date')">按日</div>
</div>
<div class="tab-content w-ull">
<date-picker
v-if="type == 'year'"
:value="value"
type="year"
:format="Format.year"
:value-format="ValueFormat.year"
@input="handleChange"
>
</date-picker>
<date-picker
v-if="type == 'month'"
:value="value"
type="month"
:format="Format.month"
:value-format="ValueFormat.month"
@input="handleChange"
>
</date-picker>
<date-picker
v-if="type == 'date'"
:value="value"
type="date"
:format="Format.date"
:value-format="ValueFormat.date"
@input="handleChange"
>
</date-picker>
</div>
</div>
<el-input
:size="size"
:placeholder="placeholder"
:value="dateInputVal"
slot="reference"
:clearable="clearable"
@clear="handleClearDate"
></el-input>
</el-popover>
</template>
<script>
import DatePicker from './date-picker/src/picker/date-picker';
export default {
model: {
value: 'value',
event: 'change'
},
components: {
DatePicker
},
props: {
value: {
type: String,
default: ''
},
size: {
type: String,
default: ''
},
placement: {
type: String,
default: 'bottom-start'
},
placeholder: {
type: String,
default: '请选择'
},
clearable: {
type: Boolean,
default: true
},
format: {
type: Object,
default: () => {}
},
valueFormat: {
type: Object,
default: () => {}
}
},
data() {
return {
type: 'year'
};
},
computed: {
dateInputVal() {
return this.value;
},
Format() {
return {
year: 'yyyy',
month: 'MM',
date: 'yyyy-MM-dd',
...this.format
};
},
ValueFormat() {
return {
year: 'yyyy',
month: 'yyyy-MM',
date: 'yyyy-MM-dd',
...this.valueFormat
};
}
},
watch: {
value: {
handler(val) {
let format = this.getDateType(val);
if (format === 'YYYY-MM-DD') {
this.type = 'date';
} else if (format === 'YYYY-MM') {
this.type = 'month';
} else if (format === 'YYYY') {
this.type = 'year';
}
},
immediate: true
}
},
methods: {
changeTab(key) {
this.$emit('change', '');
this.type = key;
// 更新popover定位
this.$nextTick(() => {
if (this.$refs.popover) {
this.$refs.popover.updatePopper();
}
});
},
handleClearDate() {
this.$emit('change', '');
},
handleChange(val) {
this.$emit('change', val);
},
getDateType(dateStr) {
if (dateStr) {
// 替换中文的年、月、日为标准分隔符
const cleanedDateStr = dateStr.replace('', '-').replace('', '-').replace('', '');
const formats = ['YYYY-MM-DD', 'YYYY-MM', 'YYYY'];
// 尝试用不同的格式解析
for (const format of formats) {
const date = this.$moment(cleanedDateStr, format, true); // 严格模式解析
if (date.isValid()) {
return format;
}
}
return 'Invalid Date'; // 如果都无法解析,则认为是无效日期
}
}
}
};
</script>
<style lang="less" scoped>
.tab-box {
width: 300px;
height: 40px;
background-color: #ebebeb;
border-radius: 20px;
display: flex;
position: relative;
.tab-item {
width: 100px;
height: 100%;
border-radius: 20px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.active {
background-color: #fff;
border: 2px solid var(--primary);
}
}
:deep(.el-picker-panel) {
border: none;
box-shadow: none;
border-radius: 0px;
}
:global(.y-date-popover) {
width: 348px;
min-height: 330px;
display: flex;
}
</style>
import DatePicker from './src/picker/date-picker';
/* istanbul ignore next */
DatePicker.install = function install(Vue) {
Vue.component(DatePicker.name, DatePicker);
};
export default DatePicker;
<template>
<table
cellspacing="0"
cellpadding="0"
class="el-date-table"
@click="handleClick"
@mousemove="handleMouseMove"
:class="{ 'is-week-mode': selectionMode === 'week' }"
>
<tbody>
<tr>
<th v-if="showWeekNumber">{{ t("el.datepicker.week") }}</th>
<th v-for="(week, key) in WEEKS" :key="key">
{{ t("el.datepicker.weeks." + week) }}
</th>
</tr>
<tr
class="el-date-table__row"
v-for="(row, key) in rows"
:class="{ current: isWeekActive(row[1]) }"
:key="key"
>
<td v-for="(cell, key) in row" :class="getCellClasses(cell)" :key="key">
<div>
<span>
{{ cell.text }}
</span>
</div>
</td>
</tr>
</tbody>
</table>
</template>
<script>
/* eslint-disable */
import {
getFirstDayOfMonth,
getDayCountOfMonth,
getWeekNumber,
getStartDateOfMonth,
prevDate,
nextDate,
isDate,
clearTime as _clearTime,
} from "element-ui/src/utils/date-util";
import Locale from "element-ui/src/mixins/locale";
import {
arrayFindIndex,
arrayFind,
coerceTruthyValueToArray,
} from "element-ui/src/utils/util";
const WEEKS = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"];
const getDateTimestamp = function (time) {
if (typeof time === "number" || typeof time === "string") {
return _clearTime(new Date(time)).getTime();
} else if (time instanceof Date) {
return _clearTime(time).getTime();
} else {
return NaN;
}
};
// remove the first element that satisfies `pred` from arr
// return a new array if modification occurs
// return the original array otherwise
const removeFromArray = function (arr, pred) {
const idx =
typeof pred === "function" ? arrayFindIndex(arr, pred) : arr.indexOf(pred);
return idx >= 0 ? [...arr.slice(0, idx), ...arr.slice(idx + 1)] : arr;
};
export default {
mixins: [Locale],
props: {
firstDayOfWeek: {
default: 7,
type: Number,
validator: (val) => val >= 1 && val <= 7,
},
value: {},
defaultValue: {
validator(val) {
// either: null, valid Date object, Array of valid Date objects
return (
val === null ||
isDate(val) ||
(Array.isArray(val) && val.every(isDate))
);
},
},
date: {},
selectionMode: {
default: "day",
},
showWeekNumber: {
type: Boolean,
default: false,
},
disabledDate: {},
cellClassName: {},
minDate: {},
maxDate: {},
rangeState: {
default() {
return {
endDate: null,
selecting: false,
};
},
},
},
computed: {
offsetDay() {
const week = this.firstDayOfWeek;
// 周日为界限,左右偏移的天数,3217654 例如周一就是 -1,目的是调整前两行日期的位置
return week > 3 ? 7 - week : -week;
},
WEEKS() {
const week = this.firstDayOfWeek;
return WEEKS.concat(WEEKS).slice(week, week + 7);
},
year() {
return this.date.getFullYear();
},
month() {
return this.date.getMonth();
},
startDate() {
return getStartDateOfMonth(this.year, this.month);
},
rows() {
// TODO: refactory rows / getCellClasses
const date = new Date(this.year, this.month, 1);
let day = getFirstDayOfMonth(date); // day of first day
const dateCountOfMonth = getDayCountOfMonth(
date.getFullYear(),
date.getMonth()
);
const dateCountOfLastMonth = getDayCountOfMonth(
date.getFullYear(),
date.getMonth() === 0 ? 11 : date.getMonth() - 1
);
day = day === 0 ? 7 : day;
const offset = this.offsetDay;
const rows = this.tableRows;
let count = 1;
const startDate = this.startDate;
const disabledDate = this.disabledDate;
const cellClassName = this.cellClassName;
const selectedDate =
this.selectionMode === "dates"
? coerceTruthyValueToArray(this.value)
: [];
const now = getDateTimestamp(new Date());
for (let i = 0; i < 6; i++) {
const row = rows[i];
if (this.showWeekNumber) {
if (!row[0]) {
row[0] = {
type: "week",
text: getWeekNumber(nextDate(startDate, i * 7 + 1)),
};
}
}
for (let j = 0; j < 7; j++) {
let cell = row[this.showWeekNumber ? j + 1 : j];
if (!cell) {
cell = {
row: i,
column: j,
type: "normal",
inRange: false,
start: false,
end: false,
};
}
cell.type = "normal";
const index = i * 7 + j;
const time = nextDate(startDate, index - offset).getTime();
cell.inRange =
time >= getDateTimestamp(this.minDate) &&
time <= getDateTimestamp(this.maxDate);
cell.start = this.minDate && time === getDateTimestamp(this.minDate);
cell.end = this.maxDate && time === getDateTimestamp(this.maxDate);
const isToday = time === now;
if (isToday) {
cell.type = "today";
}
if (i >= 0 && i <= 1) {
const numberOfDaysFromPreviousMonth =
day + offset < 0 ? 7 + day + offset : day + offset;
if (j + i * 7 >= numberOfDaysFromPreviousMonth) {
cell.text = count++;
} else {
cell.text =
dateCountOfLastMonth -
(numberOfDaysFromPreviousMonth - (j % 7)) +
1 +
i * 7;
cell.type = "prev-month";
}
} else {
if (count <= dateCountOfMonth) {
cell.text = count++;
} else {
cell.text = count++ - dateCountOfMonth;
cell.type = "next-month";
}
}
let cellDate = new Date(time);
cell.disabled =
typeof disabledDate === "function" && disabledDate(cellDate);
cell.selected = arrayFind(
selectedDate,
(date) => date.getTime() === cellDate.getTime()
);
cell.customClass =
typeof cellClassName === "function" && cellClassName(cellDate);
this.$set(row, this.showWeekNumber ? j + 1 : j, cell);
}
if (this.selectionMode === "week") {
const start = this.showWeekNumber ? 1 : 0;
const end = this.showWeekNumber ? 7 : 6;
const isWeekActive = this.isWeekActive(row[start + 1]);
row[start].inRange = isWeekActive;
row[start].start = isWeekActive;
row[end].inRange = isWeekActive;
row[end].end = isWeekActive;
}
}
return rows;
},
},
watch: {
"rangeState.endDate"(newVal) {
this.markRange(this.minDate, newVal);
},
minDate(newVal, oldVal) {
if (getDateTimestamp(newVal) !== getDateTimestamp(oldVal)) {
this.markRange(this.minDate, this.maxDate);
}
},
maxDate(newVal, oldVal) {
if (getDateTimestamp(newVal) !== getDateTimestamp(oldVal)) {
this.markRange(this.minDate, this.maxDate);
}
},
},
data() {
return {
tableRows: [[], [], [], [], [], []],
lastRow: null,
lastColumn: null,
};
},
methods: {
cellMatchesDate(cell, date) {
const value = new Date(date);
return (
this.year === value.getFullYear() &&
this.month === value.getMonth() &&
Number(cell.text) === value.getDate()
);
},
getCellClasses(cell) {
const selectionMode = this.selectionMode;
const defaultValue = this.defaultValue
? Array.isArray(this.defaultValue)
? this.defaultValue
: [this.defaultValue]
: [];
let classes = [];
if ((cell.type === "normal" || cell.type === "today") && !cell.disabled) {
classes.push("available");
if (cell.type === "today") {
classes.push("today");
}
} else {
classes.push(cell.type);
}
if (
cell.type === "normal" &&
defaultValue.some((date) => this.cellMatchesDate(cell, date))
) {
classes.push("default");
}
if (
selectionMode === "day" &&
(cell.type === "normal" || cell.type === "today") &&
this.cellMatchesDate(cell, this.value)
) {
classes.push("current");
}
if (
cell.inRange &&
(cell.type === "normal" ||
cell.type === "today" ||
this.selectionMode === "week")
) {
classes.push("in-range");
if (cell.start) {
classes.push("start-date");
}
if (cell.end) {
classes.push("end-date");
}
}
if (cell.disabled) {
classes.push("disabled");
}
if (cell.selected) {
classes.push("selected");
}
if (cell.customClass) {
classes.push(cell.customClass);
}
return classes.join(" ");
},
getDateOfCell(row, column) {
const offsetFromStart =
row * 7 + (column - (this.showWeekNumber ? 1 : 0)) - this.offsetDay;
return nextDate(this.startDate, offsetFromStart);
},
isWeekActive(cell) {
if (this.selectionMode !== "week") return false;
const newDate = new Date(this.year, this.month, 1);
const year = newDate.getFullYear();
const month = newDate.getMonth();
if (cell.type === "prev-month") {
newDate.setMonth(month === 0 ? 11 : month - 1);
newDate.setFullYear(month === 0 ? year - 1 : year);
}
if (cell.type === "next-month") {
newDate.setMonth(month === 11 ? 0 : month + 1);
newDate.setFullYear(month === 11 ? year + 1 : year);
}
newDate.setDate(parseInt(cell.text, 10));
if (isDate(this.value)) {
const dayOffset =
((this.value.getDay() - this.firstDayOfWeek + 7) % 7) - 1;
const weekDate = prevDate(this.value, dayOffset);
return weekDate.getTime() === newDate.getTime();
}
return false;
},
markRange(minDate, maxDate) {
minDate = getDateTimestamp(minDate);
maxDate = getDateTimestamp(maxDate) || minDate;
[minDate, maxDate] = [
Math.min(minDate, maxDate),
Math.max(minDate, maxDate),
];
const startDate = this.startDate;
const rows = this.rows;
for (let i = 0, k = rows.length; i < k; i++) {
const row = rows[i];
for (let j = 0, l = row.length; j < l; j++) {
if (this.showWeekNumber && j === 0) continue;
const cell = row[j];
const index = i * 7 + j + (this.showWeekNumber ? -1 : 0);
const time = nextDate(startDate, index - this.offsetDay).getTime();
cell.inRange = minDate && time >= minDate && time <= maxDate;
cell.start = minDate && time === minDate;
cell.end = maxDate && time === maxDate;
}
}
},
handleMouseMove(event) {
if (!this.rangeState.selecting) return;
let target = event.target;
if (target.tagName === "SPAN") {
target = target.parentNode.parentNode;
}
if (target.tagName === "DIV") {
target = target.parentNode;
}
if (target.tagName !== "TD") return;
const row = target.parentNode.rowIndex - 1;
const column = target.cellIndex;
// can not select disabled date
if (this.rows[row][column].disabled) return;
// only update rangeState when mouse moves to a new cell
// this avoids frequent Date object creation and improves performance
if (row !== this.lastRow || column !== this.lastColumn) {
this.lastRow = row;
this.lastColumn = column;
this.$emit("changerange", {
minDate: this.minDate,
maxDate: this.maxDate,
rangeState: {
selecting: true,
endDate: this.getDateOfCell(row, column),
},
});
}
},
handleClick(event) {
let target = event.target;
if (target.tagName === "SPAN") {
target = target.parentNode.parentNode;
}
if (target.tagName === "DIV") {
target = target.parentNode;
}
if (target.tagName !== "TD") return;
const row = target.parentNode.rowIndex - 1;
const column = this.selectionMode === "week" ? 1 : target.cellIndex;
const cell = this.rows[row][column];
if (cell.disabled || cell.type === "week") return;
const newDate = this.getDateOfCell(row, column);
if (this.selectionMode === "range") {
if (!this.rangeState.selecting) {
this.$emit("pick", { minDate: newDate, maxDate: null });
this.rangeState.selecting = true;
} else {
if (newDate >= this.minDate) {
this.$emit("pick", { minDate: this.minDate, maxDate: newDate });
} else {
this.$emit("pick", { minDate: newDate, maxDate: this.minDate });
}
this.rangeState.selecting = false;
}
} else if (this.selectionMode === "day") {
this.$emit("pick", newDate);
} else if (this.selectionMode === "week") {
const weekNumber = getWeekNumber(newDate);
const value = newDate.getFullYear() + "w" + weekNumber;
this.$emit("pick", {
year: newDate.getFullYear(),
week: weekNumber,
value: value,
date: newDate,
});
} else if (this.selectionMode === "dates") {
const value = this.value || [];
const newValue = cell.selected
? removeFromArray(
value,
(date) => date.getTime() === newDate.getTime()
)
: [...value, newDate];
this.$emit("pick", newValue);
}
},
},
};
</script>
<template>
<table
@click="handleMonthTableClick"
@mousemove="handleMouseMove"
class="el-month-table"
>
<tbody>
<tr v-for="(row, key) in rows" :key="key">
<td :class="getCellStyle(cell)" v-for="(cell, key) in row" :key="key">
<div>
<a class="cell">{{
t("el.datepicker.months." + months[cell.text])
}}</a>
</div>
</td>
</tr>
</tbody>
</table>
</template>
<script type="text/babel">
/* eslint-disable */
import Locale from "element-ui/src/mixins/locale";
import {
isDate,
range,
getDayCountOfMonth,
nextDate,
} from "element-ui/src/utils/date-util";
import { hasClass } from "element-ui/src/utils/dom";
import {
arrayFindIndex,
coerceTruthyValueToArray,
arrayFind,
} from "element-ui/src/utils/util";
const datesInMonth = (year, month) => {
const numOfDays = getDayCountOfMonth(year, month);
const firstDay = new Date(year, month, 1);
return range(numOfDays).map((n) => nextDate(firstDay, n));
};
const clearDate = (date) => {
return new Date(date.getFullYear(), date.getMonth());
};
const getMonthTimestamp = function (time) {
if (typeof time === "number" || typeof time === "string") {
return clearDate(new Date(time)).getTime();
} else if (time instanceof Date) {
return clearDate(time).getTime();
} else {
return NaN;
}
};
// remove the first element that satisfies `pred` from arr
// return a new array if modification occurs
// return the original array otherwise
const removeFromArray = function (arr, pred) {
const idx =
typeof pred === "function" ? arrayFindIndex(arr, pred) : arr.indexOf(pred);
return idx >= 0 ? [...arr.slice(0, idx), ...arr.slice(idx + 1)] : arr;
};
export default {
props: {
disabledDate: {},
value: {},
selectionMode: {
default: "month",
},
minDate: {},
maxDate: {},
defaultValue: {
validator(val) {
// null or valid Date Object
return (
val === null ||
isDate(val) ||
(Array.isArray(val) && val.every(isDate))
);
},
},
date: {},
rangeState: {
default() {
return {
endDate: null,
selecting: false,
};
},
},
},
mixins: [Locale],
watch: {
"rangeState.endDate"(newVal) {
this.markRange(this.minDate, newVal);
},
minDate(newVal, oldVal) {
if (getMonthTimestamp(newVal) !== getMonthTimestamp(oldVal)) {
this.markRange(this.minDate, this.maxDate);
}
},
maxDate(newVal, oldVal) {
if (getMonthTimestamp(newVal) !== getMonthTimestamp(oldVal)) {
this.markRange(this.minDate, this.maxDate);
}
},
},
data() {
return {
months: [
"jan",
"feb",
"mar",
"apr",
"may",
"jun",
"jul",
"aug",
"sep",
"oct",
"nov",
"dec",
],
tableRows: [[], [], []],
lastRow: null,
lastColumn: null,
};
},
methods: {
cellMatchesDate(cell, date) {
const value = new Date(date);
return (
this.date.getFullYear() === value.getFullYear() &&
Number(cell.text) === value.getMonth()
);
},
getCellStyle(cell) {
const style = {};
const year = this.date.getFullYear();
const today = new Date();
const month = cell.text;
const defaultValue = this.defaultValue
? Array.isArray(this.defaultValue)
? this.defaultValue
: [this.defaultValue]
: [];
style.disabled =
typeof this.disabledDate === "function"
? datesInMonth(year, month).every(this.disabledDate)
: false;
style.current =
arrayFindIndex(
coerceTruthyValueToArray(this.value),
(date) => date.getFullYear() === year && date.getMonth() === month
) >= 0;
style.today = today.getFullYear() === year && today.getMonth() === month;
style.default = defaultValue.some((date) =>
this.cellMatchesDate(cell, date)
);
if (cell.inRange) {
style["in-range"] = true;
if (cell.start) {
style["start-date"] = true;
}
if (cell.end) {
style["end-date"] = true;
}
}
return style;
},
getMonthOfCell(month) {
const year = this.date.getFullYear();
return new Date(year, month, 1);
},
markRange(minDate, maxDate) {
minDate = getMonthTimestamp(minDate);
maxDate = getMonthTimestamp(maxDate) || minDate;
[minDate, maxDate] = [
Math.min(minDate, maxDate),
Math.max(minDate, maxDate),
];
const rows = this.rows;
for (let i = 0, k = rows.length; i < k; i++) {
const row = rows[i];
for (let j = 0, l = row.length; j < l; j++) {
const cell = row[j];
const index = i * 4 + j;
const time = new Date(this.date.getFullYear(), index).getTime();
cell.inRange = minDate && time >= minDate && time <= maxDate;
cell.start = minDate && time === minDate;
cell.end = maxDate && time === maxDate;
}
}
},
handleMouseMove(event) {
if (!this.rangeState.selecting) return;
let target = event.target;
if (target.tagName === "A") {
target = target.parentNode.parentNode;
}
if (target.tagName === "DIV") {
target = target.parentNode;
}
if (target.tagName !== "TD") return;
const row = target.parentNode.rowIndex;
const column = target.cellIndex;
// can not select disabled date
if (this.rows[row][column].disabled) return;
// only update rangeState when mouse moves to a new cell
// this avoids frequent Date object creation and improves performance
if (row !== this.lastRow || column !== this.lastColumn) {
this.lastRow = row;
this.lastColumn = column;
this.$emit("changerange", {
minDate: this.minDate,
maxDate: this.maxDate,
rangeState: {
selecting: true,
endDate: this.getMonthOfCell(row * 4 + column),
},
});
}
},
handleMonthTableClick(event) {
let target = event.target;
if (target.tagName === "A") {
target = target.parentNode.parentNode;
}
if (target.tagName === "DIV") {
target = target.parentNode;
}
if (target.tagName !== "TD") return;
if (hasClass(target, "disabled")) return;
const column = target.cellIndex;
const row = target.parentNode.rowIndex;
const month = row * 4 + column;
const newDate = this.getMonthOfCell(month);
if (this.selectionMode === "range") {
if (!this.rangeState.selecting) {
this.$emit("pick", { minDate: newDate, maxDate: null });
this.rangeState.selecting = true;
} else {
if (newDate >= this.minDate) {
this.$emit("pick", { minDate: this.minDate, maxDate: newDate });
} else {
this.$emit("pick", { minDate: newDate, maxDate: this.minDate });
}
this.rangeState.selecting = false;
}
} else if (this.selectionMode === "months") {
const value = this.value || [];
const year = this.date.getFullYear();
const newValue =
arrayFindIndex(
value,
(date) => date.getFullYear() === year && date.getMonth() === month
) >= 0
? removeFromArray(
value,
(date) => date.getTime() === newDate.getTime()
)
: [...value, newDate];
this.$emit("pick", newValue);
} else {
this.$emit("pick", month);
}
},
},
computed: {
rows() {
// TODO: refactory rows / getCellClasses
const rows = this.tableRows;
const disabledDate = this.disabledDate;
const selectedDate = [];
const now = getMonthTimestamp(new Date());
for (let i = 0; i < 3; i++) {
const row = rows[i];
for (let j = 0; j < 4; j++) {
let cell = row[j];
if (!cell) {
cell = {
row: i,
column: j,
type: "normal",
inRange: false,
start: false,
end: false,
};
}
cell.type = "normal";
const index = i * 4 + j;
const time = new Date(this.date.getFullYear(), index).getTime();
cell.inRange =
time >= getMonthTimestamp(this.minDate) &&
time <= getMonthTimestamp(this.maxDate);
cell.start = this.minDate && time === getMonthTimestamp(this.minDate);
cell.end = this.maxDate && time === getMonthTimestamp(this.maxDate);
const isToday = time === now;
if (isToday) {
cell.type = "today";
}
cell.text = index;
let cellDate = new Date(time);
cell.disabled =
typeof disabledDate === "function" && disabledDate(cellDate);
cell.selected = arrayFind(
selectedDate,
(date) => date.getTime() === cellDate.getTime()
);
this.$set(row, j, cell);
}
}
return rows;
},
},
};
</script>
<template>
<div class="el-time-spinner" :class="{ 'has-seconds': showSeconds }">
<template v-if="!arrowControl">
<el-scrollbar
@mouseenter.native="emitSelectRange('hours')"
@mousemove.native="adjustCurrentSpinner('hours')"
class="el-time-spinner__wrapper"
wrap-style="max-height: inherit;"
view-class="el-time-spinner__list"
noresize
tag="ul"
ref="hours"
>
<li
@click="handleClick('hours', { value: hour, disabled: disabled })"
v-for="(disabled, hour) in hoursList"
class="el-time-spinner__item"
:key="hour"
:class="{ active: hour === hours, disabled: disabled }"
>
{{ ("0" + (amPmMode ? hour % 12 || 12 : hour)).slice(-2)
}}{{ amPm(hour) }}
</li>
</el-scrollbar>
<el-scrollbar
@mouseenter.native="emitSelectRange('minutes')"
@mousemove.native="adjustCurrentSpinner('minutes')"
class="el-time-spinner__wrapper"
wrap-style="max-height: inherit;"
view-class="el-time-spinner__list"
noresize
tag="ul"
ref="minutes"
>
<li
@click="handleClick('minutes', { value: key, disabled: false })"
v-for="(enabled, key) in minutesList"
:key="key"
class="el-time-spinner__item"
:class="{ active: key === minutes, disabled: !enabled }"
>
{{ ("0" + key).slice(-2) }}
</li>
</el-scrollbar>
<el-scrollbar
v-show="showSeconds"
@mouseenter.native="emitSelectRange('seconds')"
@mousemove.native="adjustCurrentSpinner('seconds')"
class="el-time-spinner__wrapper"
wrap-style="max-height: inherit;"
view-class="el-time-spinner__list"
noresize
tag="ul"
ref="seconds"
>
<li
@click="handleClick('seconds', { value: key, disabled: false })"
v-for="(second, key) in 60"
class="el-time-spinner__item"
:class="{ active: key === seconds }"
:key="key"
>
{{ ("0" + key).slice(-2) }}
</li>
</el-scrollbar>
</template>
<template v-if="arrowControl">
<div
@mouseenter="emitSelectRange('hours')"
class="el-time-spinner__wrapper is-arrow"
>
<i
v-repeat-click="decrease"
class="el-time-spinner__arrow el-icon-arrow-up"
></i>
<i
v-repeat-click="increase"
class="el-time-spinner__arrow el-icon-arrow-down"
></i>
<ul class="el-time-spinner__list" ref="hours">
<li
class="el-time-spinner__item"
:class="{ active: hour === hours, disabled: hoursList[hour] }"
v-for="(hour, key) in arrowHourList"
:key="key"
>
{{
hour === undefined
? ""
: ("0" + (amPmMode ? hour % 12 || 12 : hour)).slice(-2) +
amPm(hour)
}}
</li>
</ul>
</div>
<div
@mouseenter="emitSelectRange('minutes')"
class="el-time-spinner__wrapper is-arrow"
>
<i
v-repeat-click="decrease"
class="el-time-spinner__arrow el-icon-arrow-up"
></i>
<i
v-repeat-click="increase"
class="el-time-spinner__arrow el-icon-arrow-down"
></i>
<ul class="el-time-spinner__list" ref="minutes">
<li
class="el-time-spinner__item"
:class="{ active: minute === minutes }"
v-for="(minute, key) in arrowMinuteList"
:key="key"
>
{{ minute === undefined ? "" : ("0" + minute).slice(-2) }}
</li>
</ul>
</div>
<div
@mouseenter="emitSelectRange('seconds')"
class="el-time-spinner__wrapper is-arrow"
v-if="showSeconds"
>
<i
v-repeat-click="decrease"
class="el-time-spinner__arrow el-icon-arrow-up"
></i>
<i
v-repeat-click="increase"
class="el-time-spinner__arrow el-icon-arrow-down"
></i>
<ul class="el-time-spinner__list" ref="seconds">
<li
v-for="(second, key) in arrowSecondList"
class="el-time-spinner__item"
:class="{ active: second === seconds }"
:key="key"
>
{{ second === undefined ? "" : ("0" + second).slice(-2) }}
</li>
</ul>
</div>
</template>
</div>
</template>
<script type="text/babel">
/* eslint-disable */
import {
getRangeHours,
getRangeMinutes,
modifyTime,
} from "element-ui/src/utils/date-util";
import ElScrollbar from "element-ui/packages/scrollbar";
import RepeatClick from "element-ui/src/directives/repeat-click";
export default {
components: { ElScrollbar },
directives: {
repeatClick: RepeatClick,
},
props: {
date: {},
defaultValue: {}, // reserved for future use
showSeconds: {
type: Boolean,
default: true,
},
arrowControl: Boolean,
amPmMode: {
type: String,
default: "", // 'a': am/pm; 'A': AM/PM
},
},
computed: {
hours() {
return this.date.getHours();
},
minutes() {
return this.date.getMinutes();
},
seconds() {
return this.date.getSeconds();
},
hoursList() {
return getRangeHours(this.selectableRange);
},
minutesList() {
return getRangeMinutes(this.selectableRange, this.hours);
},
arrowHourList() {
const hours = this.hours;
return [
hours > 0 ? hours - 1 : undefined,
hours,
hours < 23 ? hours + 1 : undefined,
];
},
arrowMinuteList() {
const minutes = this.minutes;
return [
minutes > 0 ? minutes - 1 : undefined,
minutes,
minutes < 59 ? minutes + 1 : undefined,
];
},
arrowSecondList() {
const seconds = this.seconds;
return [
seconds > 0 ? seconds - 1 : undefined,
seconds,
seconds < 59 ? seconds + 1 : undefined,
];
},
},
data() {
return {
selectableRange: [],
currentScrollbar: null,
};
},
mounted() {
this.$nextTick(() => {
!this.arrowControl && this.bindScrollEvent();
});
},
methods: {
increase() {
this.scrollDown(1);
},
decrease() {
this.scrollDown(-1);
},
modifyDateField(type, value) {
switch (type) {
case "hours":
this.$emit(
"change",
modifyTime(this.date, value, this.minutes, this.seconds)
);
break;
case "minutes":
this.$emit(
"change",
modifyTime(this.date, this.hours, value, this.seconds)
);
break;
case "seconds":
this.$emit(
"change",
modifyTime(this.date, this.hours, this.minutes, value)
);
break;
}
},
handleClick(type, { value, disabled }) {
if (!disabled) {
this.modifyDateField(type, value);
this.emitSelectRange(type);
this.adjustSpinner(type, value);
}
},
emitSelectRange(type) {
if (type === "hours") {
this.$emit("select-range", 0, 2);
} else if (type === "minutes") {
this.$emit("select-range", 3, 5);
} else if (type === "seconds") {
this.$emit("select-range", 6, 8);
}
this.currentScrollbar = type;
},
bindScrollEvent() {
const bindFunction = (type) => {
this.$refs[type].wrap.onscroll = (e) => {
// TODO: scroll is emitted when set scrollTop programatically
// should find better solutions in the future!
this.handleScroll(type, e);
};
};
bindFunction("hours");
bindFunction("minutes");
bindFunction("seconds");
},
handleScroll(type) {
const value = Math.min(
Math.round(
(this.$refs[type].wrap.scrollTop -
(this.scrollBarHeight(type) * 0.5 - 10) /
this.typeItemHeight(type) +
3) /
this.typeItemHeight(type)
),
type === "hours" ? 23 : 59
);
this.modifyDateField(type, value);
},
// NOTE: used by datetime / date-range panel
// renamed from adjustScrollTop
// should try to refactory it
adjustSpinners() {
this.adjustSpinner("hours", this.hours);
this.adjustSpinner("minutes", this.minutes);
this.adjustSpinner("seconds", this.seconds);
},
adjustCurrentSpinner(type) {
this.adjustSpinner(type, this[type]);
},
adjustSpinner(type, value) {
if (this.arrowControl) return;
const el = this.$refs[type].wrap;
if (el) {
el.scrollTop = Math.max(0, value * this.typeItemHeight(type));
}
},
scrollDown(step) {
if (!this.currentScrollbar) {
this.emitSelectRange("hours");
}
const label = this.currentScrollbar;
const hoursList = this.hoursList;
let now = this[label];
if (this.currentScrollbar === "hours") {
let total = Math.abs(step);
step = step > 0 ? 1 : -1;
let length = hoursList.length;
while (length-- && total) {
now = (now + step + hoursList.length) % hoursList.length;
if (hoursList[now]) {
continue;
}
total--;
}
if (hoursList[now]) return;
} else {
now = (now + step + 60) % 60;
}
this.modifyDateField(label, now);
this.adjustSpinner(label, now);
this.$nextTick(() => this.emitSelectRange(this.currentScrollbar));
},
amPm(hour) {
let shouldShowAmPm = this.amPmMode.toLowerCase() === "a";
if (!shouldShowAmPm) return "";
let isCapital = this.amPmMode === "A";
let content = hour < 12 ? " am" : " pm";
if (isCapital) content = content.toUpperCase();
return content;
},
typeItemHeight(type) {
return this.$refs[type].$el.querySelector("li").offsetHeight;
},
scrollBarHeight(type) {
return this.$refs[type].$el.offsetHeight;
},
},
};
</script>
<template>
<table @click="handleYearTableClick" class="el-year-table">
<tbody>
<tr>
<td class="available" :class="getCellStyle(startYear + 0)">
<a class="cell">{{ startYear }}</a>
</td>
<td class="available" :class="getCellStyle(startYear + 1)">
<a class="cell">{{ startYear + 1 }}</a>
</td>
<td class="available" :class="getCellStyle(startYear + 2)">
<a class="cell">{{ startYear + 2 }}</a>
</td>
<td class="available" :class="getCellStyle(startYear + 3)">
<a class="cell">{{ startYear + 3 }}</a>
</td>
</tr>
<tr>
<td class="available" :class="getCellStyle(startYear + 4)">
<a class="cell">{{ startYear + 4 }}</a>
</td>
<td class="available" :class="getCellStyle(startYear + 5)">
<a class="cell">{{ startYear + 5 }}</a>
</td>
<td class="available" :class="getCellStyle(startYear + 6)">
<a class="cell">{{ startYear + 6 }}</a>
</td>
<td class="available" :class="getCellStyle(startYear + 7)">
<a class="cell">{{ startYear + 7 }}</a>
</td>
</tr>
<tr>
<td class="available" :class="getCellStyle(startYear + 8)">
<a class="cell">{{ startYear + 8 }}</a>
</td>
<td class="available" :class="getCellStyle(startYear + 9)">
<a class="cell">{{ startYear + 9 }}</a>
</td>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</template>
<script type="text/babel">
/* eslint-disable */
import { hasClass } from "element-ui/src/utils/dom";
import {
isDate,
range,
nextDate,
getDayCountOfYear,
} from "element-ui/src/utils/date-util";
import {
arrayFindIndex,
coerceTruthyValueToArray,
} from "element-ui/src/utils/util";
const datesInYear = (year) => {
const numOfDays = getDayCountOfYear(year);
const firstDay = new Date(year, 0, 1);
return range(numOfDays).map((n) => nextDate(firstDay, n));
};
export default {
props: {
disabledDate: {},
value: {},
defaultValue: {
validator(val) {
// null or valid Date Object
return val === null || (val instanceof Date && isDate(val));
},
},
date: {},
selectionMode: {},
},
computed: {
startYear() {
return Math.floor(this.date.getFullYear() / 10) * 10;
},
},
methods: {
getCellStyle(year) {
const style = {};
const today = new Date();
style.disabled =
typeof this.disabledDate === "function"
? datesInYear(year).every(this.disabledDate)
: false;
style.current =
arrayFindIndex(
coerceTruthyValueToArray(this.value),
(date) => date.getFullYear() === year
) >= 0;
style.today = today.getFullYear() === year;
style.default =
this.defaultValue && this.defaultValue.getFullYear() === year;
return style;
},
handleYearTableClick(event) {
const target = event.target;
if (target.tagName === "A") {
if (hasClass(target.parentNode, "disabled")) return;
const year = target.textContent || target.innerText;
if (this.selectionMode === "years") {
const value = this.value || [];
const idx = arrayFindIndex(
value,
(date) => date.getFullYear() === Number(year)
);
const newValue =
idx > -1
? [...value.slice(0, idx), ...value.slice(idx + 1)]
: [...value, new Date(year)];
this.$emit("pick", newValue);
} else {
this.$emit("pick", Number(year));
}
}
},
},
};
</script>
<template>
<transition name="el-zoom-in-top" @after-leave="$emit('dodestroy')">
<div
v-show="visible"
class="el-picker-panel el-date-range-picker el-popper"
:class="[
{
'has-sidebar': $slots.sidebar || shortcuts,
'has-time': showTime,
},
popperClass,
]"
>
<div class="el-picker-panel__body-wrapper">
<slot name="sidebar" class="el-picker-panel__sidebar"></slot>
<div class="el-picker-panel__sidebar" v-if="shortcuts">
<button
type="button"
class="el-picker-panel__shortcut"
v-for="(shortcut, key) in shortcuts"
:key="key"
@click="handleShortcutClick(shortcut)"
>
{{ shortcut.text }}
</button>
</div>
<div class="el-picker-panel__body">
<div class="el-date-range-picker__time-header" v-if="showTime">
<span class="el-date-range-picker__editors-wrap">
<span class="el-date-range-picker__time-picker-wrap">
<el-input
size="small"
:disabled="rangeState.selecting"
ref="minInput"
:placeholder="t('el.datepicker.startDate')"
class="el-date-range-picker__editor"
:value="minVisibleDate"
@input="(val) => handleDateInput(val, 'min')"
@change="(val) => handleDateChange(val, 'min')"
/>
</span>
<span
class="el-date-range-picker__time-picker-wrap"
v-clickoutside="handleMinTimeClose"
>
<el-input
size="small"
class="el-date-range-picker__editor"
:disabled="rangeState.selecting"
:placeholder="t('el.datepicker.startTime')"
:value="minVisibleTime"
@focus="minTimePickerVisible = true"
@input="(val) => handleTimeInput(val, 'min')"
@change="(val) => handleTimeChange(val, 'min')"
/>
<time-picker
ref="minTimePicker"
@pick="handleMinTimePick"
:time-arrow-control="arrowControl"
:visible="minTimePickerVisible"
@mounted="$refs.minTimePicker.format = timeFormat"
>
</time-picker>
</span>
</span>
<span class="el-icon-arrow-right"></span>
<span class="el-date-range-picker__editors-wrap is-right">
<span class="el-date-range-picker__time-picker-wrap">
<el-input
size="small"
class="el-date-range-picker__editor"
:disabled="rangeState.selecting"
:placeholder="t('el.datepicker.endDate')"
:value="maxVisibleDate"
:readonly="!minDate"
@input="(val) => handleDateInput(val, 'max')"
@change="(val) => handleDateChange(val, 'max')"
/>
</span>
<span
class="el-date-range-picker__time-picker-wrap"
v-clickoutside="handleMaxTimeClose"
>
<el-input
size="small"
class="el-date-range-picker__editor"
:disabled="rangeState.selecting"
:placeholder="t('el.datepicker.endTime')"
:value="maxVisibleTime"
:readonly="!minDate"
@focus="minDate && (maxTimePickerVisible = true)"
@input="(val) => handleTimeInput(val, 'max')"
@change="(val) => handleTimeChange(val, 'max')"
/>
<time-picker
ref="maxTimePicker"
@pick="handleMaxTimePick"
:time-arrow-control="arrowControl"
:visible="maxTimePickerVisible"
@mounted="$refs.maxTimePicker.format = timeFormat"
>
</time-picker>
</span>
</span>
</div>
<div
class="el-picker-panel__content el-date-range-picker__content is-left"
>
<div class="el-date-range-picker__header">
<button
type="button"
@click="leftPrevYear"
class="el-picker-panel__icon-btn el-icon-d-arrow-left"
></button>
<button
type="button"
@click="leftPrevMonth"
class="el-picker-panel__icon-btn el-icon-arrow-left"
></button>
<button
type="button"
@click="leftNextYear"
v-if="unlinkPanels"
:disabled="!enableYearArrow"
:class="{ 'is-disabled': !enableYearArrow }"
class="el-picker-panel__icon-btn el-icon-d-arrow-right"
></button>
<button
type="button"
@click="leftNextMonth"
v-if="unlinkPanels"
:disabled="!enableMonthArrow"
:class="{ 'is-disabled': !enableMonthArrow }"
class="el-picker-panel__icon-btn el-icon-arrow-right"
></button>
<div>{{ leftLabel }}</div>
</div>
<date-table
selection-mode="range"
:date="leftDate"
:default-value="defaultValue"
:min-date="minDate"
:max-date="maxDate"
:range-state="rangeState"
:disabled-date="disabledDate"
:cell-class-name="cellClassName"
@changerange="handleChangeRange"
:first-day-of-week="firstDayOfWeek"
@pick="handleRangePick"
>
</date-table>
</div>
<div
class="el-picker-panel__content el-date-range-picker__content is-right"
>
<div class="el-date-range-picker__header">
<button
type="button"
@click="rightPrevYear"
v-if="unlinkPanels"
:disabled="!enableYearArrow"
:class="{ 'is-disabled': !enableYearArrow }"
class="el-picker-panel__icon-btn el-icon-d-arrow-left"
></button>
<button
type="button"
@click="rightPrevMonth"
v-if="unlinkPanels"
:disabled="!enableMonthArrow"
:class="{ 'is-disabled': !enableMonthArrow }"
class="el-picker-panel__icon-btn el-icon-arrow-left"
></button>
<button
type="button"
@click="rightNextYear"
class="el-picker-panel__icon-btn el-icon-d-arrow-right"
></button>
<button
type="button"
@click="rightNextMonth"
class="el-picker-panel__icon-btn el-icon-arrow-right"
></button>
<div>{{ rightLabel }}</div>
</div>
<date-table
selection-mode="range"
:date="rightDate"
:default-value="defaultValue"
:min-date="minDate"
:max-date="maxDate"
:range-state="rangeState"
:disabled-date="disabledDate"
:cell-class-name="cellClassName"
@changerange="handleChangeRange"
:first-day-of-week="firstDayOfWeek"
@pick="handleRangePick"
>
</date-table>
</div>
</div>
</div>
<div class="el-picker-panel__footer" v-if="showTime">
<el-button
size="mini"
type="text"
class="el-picker-panel__link-btn"
@click="handleClear"
>
{{ t("el.datepicker.clear") }}
</el-button>
<el-button
plain
size="mini"
class="el-picker-panel__link-btn"
:disabled="btnDisabled"
@click="handleConfirm(false)"
>
{{ t("el.datepicker.confirm") }}
</el-button>
</div>
</div>
</transition>
</template>
<script type="text/babel">
/* eslint-disable */
import {
formatDate,
parseDate,
isDate,
modifyDate,
modifyTime,
modifyWithTimeString,
prevYear,
nextYear,
prevMonth,
nextMonth,
nextDate,
extractDateFormat,
extractTimeFormat,
} from "element-ui/src/utils/date-util";
import Clickoutside from "element-ui/src/utils/clickoutside";
import Locale from "element-ui/src/mixins/locale";
import TimePicker from "./time";
import DateTable from "../basic/date-table";
import ElInput from "element-ui/packages/input";
import ElButton from "element-ui/packages/button";
const calcDefaultValue = (defaultValue) => {
if (Array.isArray(defaultValue)) {
return [new Date(defaultValue[0]), new Date(defaultValue[1])];
} else if (defaultValue) {
return [new Date(defaultValue), nextDate(new Date(defaultValue), 1)];
} else {
return [new Date(), nextDate(new Date(), 1)];
}
};
export default {
mixins: [Locale],
directives: { Clickoutside },
computed: {
btnDisabled() {
return !(
this.minDate &&
this.maxDate &&
!this.selecting &&
this.isValidValue([this.minDate, this.maxDate])
);
},
leftLabel() {
return (
this.leftDate.getFullYear() +
" " +
this.t("el.datepicker.year") +
" " +
this.t(`el.datepicker.month${this.leftDate.getMonth() + 1}`)
);
},
rightLabel() {
return (
this.rightDate.getFullYear() +
" " +
this.t("el.datepicker.year") +
" " +
this.t(`el.datepicker.month${this.rightDate.getMonth() + 1}`)
);
},
leftYear() {
return this.leftDate.getFullYear();
},
leftMonth() {
return this.leftDate.getMonth();
},
leftMonthDate() {
return this.leftDate.getDate();
},
rightYear() {
return this.rightDate.getFullYear();
},
rightMonth() {
return this.rightDate.getMonth();
},
rightMonthDate() {
return this.rightDate.getDate();
},
minVisibleDate() {
if (this.dateUserInput.min !== null) return this.dateUserInput.min;
if (this.minDate) return formatDate(this.minDate, this.dateFormat);
return "";
},
maxVisibleDate() {
if (this.dateUserInput.max !== null) return this.dateUserInput.max;
if (this.maxDate || this.minDate)
return formatDate(this.maxDate || this.minDate, this.dateFormat);
return "";
},
minVisibleTime() {
if (this.timeUserInput.min !== null) return this.timeUserInput.min;
if (this.minDate) return formatDate(this.minDate, this.timeFormat);
return "";
},
maxVisibleTime() {
if (this.timeUserInput.max !== null) return this.timeUserInput.max;
if (this.maxDate || this.minDate)
return formatDate(this.maxDate || this.minDate, this.timeFormat);
return "";
},
timeFormat() {
if (this.format) {
return extractTimeFormat(this.format);
} else {
return "HH:mm:ss";
}
},
dateFormat() {
if (this.format) {
return extractDateFormat(this.format);
} else {
return "yyyy-MM-dd";
}
},
enableMonthArrow() {
const nextMonth = (this.leftMonth + 1) % 12;
const yearOffset = this.leftMonth + 1 >= 12 ? 1 : 0;
return (
this.unlinkPanels &&
new Date(this.leftYear + yearOffset, nextMonth) <
new Date(this.rightYear, this.rightMonth)
);
},
enableYearArrow() {
return (
this.unlinkPanels &&
this.rightYear * 12 +
this.rightMonth -
(this.leftYear * 12 + this.leftMonth + 1) >=
12
);
},
},
data() {
return {
popperClass: "",
value: [],
defaultValue: null,
defaultTime: null,
minDate: "",
maxDate: "",
leftDate: new Date(),
rightDate: nextMonth(new Date()),
rangeState: {
endDate: null,
selecting: false,
row: null,
column: null,
},
showTime: false,
shortcuts: "",
visible: "",
disabledDate: "",
cellClassName: "",
firstDayOfWeek: 7,
minTimePickerVisible: false,
maxTimePickerVisible: false,
format: "",
arrowControl: false,
unlinkPanels: false,
dateUserInput: {
min: null,
max: null,
},
timeUserInput: {
min: null,
max: null,
},
};
},
watch: {
minDate(val) {
this.dateUserInput.min = null;
this.timeUserInput.min = null;
this.$nextTick(() => {
if (
this.$refs.maxTimePicker &&
this.maxDate &&
this.maxDate < this.minDate
) {
const format = "HH:mm:ss";
this.$refs.maxTimePicker.selectableRange = [
[
parseDate(formatDate(this.minDate, format), format),
parseDate("23:59:59", format),
],
];
}
});
if (val && this.$refs.minTimePicker) {
this.$refs.minTimePicker.date = val;
this.$refs.minTimePicker.value = val;
}
},
maxDate(val) {
this.dateUserInput.max = null;
this.timeUserInput.max = null;
if (val && this.$refs.maxTimePicker) {
this.$refs.maxTimePicker.date = val;
this.$refs.maxTimePicker.value = val;
}
},
minTimePickerVisible(val) {
if (val) {
this.$nextTick(() => {
this.$refs.minTimePicker.date = this.minDate;
this.$refs.minTimePicker.value = this.minDate;
this.$refs.minTimePicker.adjustSpinners();
});
}
},
maxTimePickerVisible(val) {
if (val) {
this.$nextTick(() => {
this.$refs.maxTimePicker.date = this.maxDate;
this.$refs.maxTimePicker.value = this.maxDate;
this.$refs.maxTimePicker.adjustSpinners();
});
}
},
value(newVal) {
if (!newVal) {
this.minDate = null;
this.maxDate = null;
} else if (Array.isArray(newVal)) {
this.minDate = isDate(newVal[0]) ? new Date(newVal[0]) : null;
this.maxDate = isDate(newVal[1]) ? new Date(newVal[1]) : null;
if (this.minDate) {
this.leftDate = this.minDate;
if (this.unlinkPanels && this.maxDate) {
const minDateYear = this.minDate.getFullYear();
const minDateMonth = this.minDate.getMonth();
const maxDateYear = this.maxDate.getFullYear();
const maxDateMonth = this.maxDate.getMonth();
this.rightDate =
minDateYear === maxDateYear && minDateMonth === maxDateMonth
? nextMonth(this.maxDate)
: this.maxDate;
} else {
this.rightDate = nextMonth(this.leftDate);
}
} else {
this.leftDate = calcDefaultValue(this.defaultValue)[0];
this.rightDate = nextMonth(this.leftDate);
}
}
},
defaultValue(val) {
if (!Array.isArray(this.value)) {
const [left, right] = calcDefaultValue(val);
this.leftDate = left;
this.rightDate =
val && val[1] && this.unlinkPanels ? right : nextMonth(this.leftDate);
}
},
},
methods: {
handleClear() {
this.minDate = null;
this.maxDate = null;
this.leftDate = calcDefaultValue(this.defaultValue)[0];
this.rightDate = nextMonth(this.leftDate);
this.$emit("pick", null);
},
handleChangeRange(val) {
this.minDate = val.minDate;
this.maxDate = val.maxDate;
this.rangeState = val.rangeState;
},
handleDateInput(value, type) {
this.dateUserInput[type] = value;
if (value.length !== this.dateFormat.length) return;
const parsedValue = parseDate(value, this.dateFormat);
if (parsedValue) {
if (
typeof this.disabledDate === "function" &&
this.disabledDate(new Date(parsedValue))
) {
return;
}
if (type === "min") {
this.minDate = modifyDate(
this.minDate || new Date(),
parsedValue.getFullYear(),
parsedValue.getMonth(),
parsedValue.getDate()
);
this.leftDate = new Date(parsedValue);
if (!this.unlinkPanels) {
this.rightDate = nextMonth(this.leftDate);
}
} else {
this.maxDate = modifyDate(
this.maxDate || new Date(),
parsedValue.getFullYear(),
parsedValue.getMonth(),
parsedValue.getDate()
);
this.rightDate = new Date(parsedValue);
if (!this.unlinkPanels) {
this.leftDate = prevMonth(parsedValue);
}
}
}
},
handleDateChange(value, type) {
const parsedValue = parseDate(value, this.dateFormat);
if (parsedValue) {
if (type === "min") {
this.minDate = modifyDate(
this.minDate,
parsedValue.getFullYear(),
parsedValue.getMonth(),
parsedValue.getDate()
);
if (this.minDate > this.maxDate) {
this.maxDate = this.minDate;
}
} else {
this.maxDate = modifyDate(
this.maxDate,
parsedValue.getFullYear(),
parsedValue.getMonth(),
parsedValue.getDate()
);
if (this.maxDate < this.minDate) {
this.minDate = this.maxDate;
}
}
}
},
handleTimeInput(value, type) {
this.timeUserInput[type] = value;
if (value.length !== this.timeFormat.length) return;
const parsedValue = parseDate(value, this.timeFormat);
if (parsedValue) {
if (type === "min") {
this.minDate = modifyTime(
this.minDate,
parsedValue.getHours(),
parsedValue.getMinutes(),
parsedValue.getSeconds()
);
this.$nextTick((_) => this.$refs.minTimePicker.adjustSpinners());
} else {
this.maxDate = modifyTime(
this.maxDate,
parsedValue.getHours(),
parsedValue.getMinutes(),
parsedValue.getSeconds()
);
this.$nextTick((_) => this.$refs.maxTimePicker.adjustSpinners());
}
}
},
handleTimeChange(value, type) {
const parsedValue = parseDate(value, this.timeFormat);
if (parsedValue) {
if (type === "min") {
this.minDate = modifyTime(
this.minDate,
parsedValue.getHours(),
parsedValue.getMinutes(),
parsedValue.getSeconds()
);
if (this.minDate > this.maxDate) {
this.maxDate = this.minDate;
}
this.$refs.minTimePicker.value = this.minDate;
this.minTimePickerVisible = false;
} else {
this.maxDate = modifyTime(
this.maxDate,
parsedValue.getHours(),
parsedValue.getMinutes(),
parsedValue.getSeconds()
);
if (this.maxDate < this.minDate) {
this.minDate = this.maxDate;
}
this.$refs.maxTimePicker.value = this.minDate;
this.maxTimePickerVisible = false;
}
}
},
handleRangePick(val, close = true) {
const defaultTime = this.defaultTime || [];
const minDate = modifyWithTimeString(val.minDate, defaultTime[0]);
const maxDate = modifyWithTimeString(val.maxDate, defaultTime[1]);
if (this.maxDate === maxDate && this.minDate === minDate) {
return;
}
this.onPick && this.onPick(val);
this.maxDate = maxDate;
this.minDate = minDate;
// workaround for https://github.com/ElemeFE/element/issues/7539, should remove this block when we don't have to care about Chromium 55 - 57
setTimeout(() => {
this.maxDate = maxDate;
this.minDate = minDate;
}, 10);
if (!close || this.showTime) return;
this.handleConfirm();
},
handleShortcutClick(shortcut) {
if (shortcut.onClick) {
shortcut.onClick(this);
}
},
handleMinTimePick(value, visible, first) {
this.minDate = this.minDate || new Date();
if (value) {
this.minDate = modifyTime(
this.minDate,
value.getHours(),
value.getMinutes(),
value.getSeconds()
);
}
if (!first) {
this.minTimePickerVisible = visible;
}
if (
!this.maxDate ||
(this.maxDate && this.maxDate.getTime() < this.minDate.getTime())
) {
this.maxDate = new Date(this.minDate);
}
},
handleMinTimeClose() {
this.minTimePickerVisible = false;
},
handleMaxTimePick(value, visible, first) {
if (this.maxDate && value) {
this.maxDate = modifyTime(
this.maxDate,
value.getHours(),
value.getMinutes(),
value.getSeconds()
);
}
if (!first) {
this.maxTimePickerVisible = visible;
}
if (
this.maxDate &&
this.minDate &&
this.minDate.getTime() > this.maxDate.getTime()
) {
this.minDate = new Date(this.maxDate);
}
},
handleMaxTimeClose() {
this.maxTimePickerVisible = false;
},
// leftPrev*, rightNext* need to take care of `unlinkPanels`
leftPrevYear() {
this.leftDate = prevYear(this.leftDate);
if (!this.unlinkPanels) {
this.rightDate = nextMonth(this.leftDate);
}
},
leftPrevMonth() {
this.leftDate = prevMonth(this.leftDate);
if (!this.unlinkPanels) {
this.rightDate = nextMonth(this.leftDate);
}
},
rightNextYear() {
if (!this.unlinkPanels) {
this.leftDate = nextYear(this.leftDate);
this.rightDate = nextMonth(this.leftDate);
} else {
this.rightDate = nextYear(this.rightDate);
}
},
rightNextMonth() {
if (!this.unlinkPanels) {
this.leftDate = nextMonth(this.leftDate);
this.rightDate = nextMonth(this.leftDate);
} else {
this.rightDate = nextMonth(this.rightDate);
}
},
// leftNext*, rightPrev* are called when `unlinkPanels` is true
leftNextYear() {
this.leftDate = nextYear(this.leftDate);
},
leftNextMonth() {
this.leftDate = nextMonth(this.leftDate);
},
rightPrevYear() {
this.rightDate = prevYear(this.rightDate);
},
rightPrevMonth() {
this.rightDate = prevMonth(this.rightDate);
},
handleConfirm(visible = false) {
if (this.isValidValue([this.minDate, this.maxDate])) {
this.$emit("pick", [this.minDate, this.maxDate], visible);
}
},
isValidValue(value) {
return (
Array.isArray(value) &&
value &&
value[0] &&
value[1] &&
isDate(value[0]) &&
isDate(value[1]) &&
value[0].getTime() <= value[1].getTime() &&
(typeof this.disabledDate === "function"
? !this.disabledDate(value[0]) && !this.disabledDate(value[1])
: true)
);
},
resetView() {
// NOTE: this is a hack to reset {min, max}Date on picker open.
// TODO: correct way of doing so is to refactor {min, max}Date to be dependent on value and internal selection state
// an alternative would be resetView whenever picker becomes visible, should also investigate date-panel's resetView
if (this.minDate && this.maxDate == null)
this.rangeState.selecting = false;
this.minDate =
this.value && isDate(this.value[0]) ? new Date(this.value[0]) : null;
this.maxDate =
this.value && isDate(this.value[0]) ? new Date(this.value[1]) : null;
},
},
components: { TimePicker, DateTable, ElInput, ElButton },
};
</script>
<template>
<transition
name="el-zoom-in-top"
@after-enter="handleEnter"
@after-leave="handleLeave"
>
<div
v-show="visible"
class="el-picker-panel el-date-picker el-popper"
:class="[
{
'has-sidebar': $slots.sidebar || shortcuts,
'has-time': showTime,
},
popperClass,
]"
>
<div class="el-picker-panel__body-wrapper">
<slot name="sidebar" class="el-picker-panel__sidebar"></slot>
<div class="el-picker-panel__sidebar" v-if="shortcuts">
<button
type="button"
class="el-picker-panel__shortcut"
v-for="(shortcut, key) in shortcuts"
:key="key"
@click="handleShortcutClick(shortcut)"
>
{{ shortcut.text }}
</button>
</div>
<div class="el-picker-panel__body">
<div class="el-date-picker__time-header" v-if="showTime">
<span class="el-date-picker__editor-wrap">
<el-input
:placeholder="t('el.datepicker.selectDate')"
:value="visibleDate"
size="small"
@input="(val) => (userInputDate = val)"
@change="handleVisibleDateChange"
/>
</span>
<span
class="el-date-picker__editor-wrap"
v-clickoutside="handleTimePickClose"
>
<el-input
ref="input"
@focus="timePickerVisible = true"
:placeholder="t('el.datepicker.selectTime')"
:value="visibleTime"
size="small"
@input="(val) => (userInputTime = val)"
@change="handleVisibleTimeChange"
/>
<time-picker
ref="timepicker"
:time-arrow-control="arrowControl"
@pick="handleTimePick"
:visible="timePickerVisible"
@mounted="proxyTimePickerDataProperties"
>
</time-picker>
</span>
</div>
<div
class="el-date-picker__header"
:class="{
'el-date-picker__header--bordered':
currentView === 'year' || currentView === 'month',
}"
v-show="currentView !== 'time'"
>
<button
type="button"
@click="prevYear"
:aria-label="t(`el.datepicker.prevYear`)"
class="el-picker-panel__icon-btn el-date-picker__prev-btn el-icon-d-arrow-left"
></button>
<button
type="button"
@click="prevMonth"
v-show="currentView === 'date'"
:aria-label="t(`el.datepicker.prevMonth`)"
class="el-picker-panel__icon-btn el-date-picker__prev-btn el-icon-arrow-left"
></button>
<span
@click="showYearPicker"
role="button"
class="el-date-picker__header-label"
>{{ yearLabel }}</span
>
<span
@click="showMonthPicker"
v-show="currentView === 'date'"
role="button"
class="el-date-picker__header-label"
:class="{ active: currentView === 'month' }"
>{{ t(`el.datepicker.month${month + 1}`) }}</span
>
<button
type="button"
@click="nextYear"
:aria-label="t(`el.datepicker.nextYear`)"
class="el-picker-panel__icon-btn el-date-picker__next-btn el-icon-d-arrow-right"
></button>
<button
type="button"
@click="nextMonth"
v-show="currentView === 'date'"
:aria-label="t(`el.datepicker.nextMonth`)"
class="el-picker-panel__icon-btn el-date-picker__next-btn el-icon-arrow-right"
></button>
</div>
<div class="el-picker-panel__content">
<date-table
v-show="currentView === 'date'"
@pick="handleDatePick"
:selection-mode="selectionMode"
:first-day-of-week="firstDayOfWeek"
:value="value"
:default-value="defaultValue ? new Date(defaultValue) : null"
:date="date"
:cell-class-name="cellClassName"
:disabled-date="disabledDate"
>
</date-table>
<year-table
v-show="currentView === 'year'"
@pick="handleYearPick"
:selection-mode="selectionMode"
:value="value"
:default-value="defaultValue ? new Date(defaultValue) : null"
:date="date"
:disabled-date="disabledDate"
>
</year-table>
<month-table
v-show="currentView === 'month'"
@pick="handleMonthPick"
:selection-mode="selectionMode"
:value="value"
:default-value="defaultValue ? new Date(defaultValue) : null"
:date="date"
:disabled-date="disabledDate"
>
</month-table>
</div>
</div>
</div>
<div
class="el-picker-panel__footer"
v-show="
footerVisible &&
(currentView === 'date' ||
currentView === 'month' ||
currentView === 'year')
"
>
<el-button
size="mini"
type="text"
class="el-picker-panel__link-btn"
@click="changeToNow"
v-show="
selectionMode !== 'dates' &&
selectionMode !== 'months' &&
selectionMode !== 'years'
"
>
{{ t("el.datepicker.now") }}
</el-button>
<el-button
plain
size="mini"
class="el-picker-panel__link-btn"
@click="confirm"
>
{{ t("el.datepicker.confirm") }}
</el-button>
</div>
</div>
</transition>
</template>
<script type="text/babel">
/* eslint-disable */
import {
formatDate,
parseDate,
getWeekNumber,
isDate,
modifyDate,
modifyTime,
modifyWithTimeString,
clearMilliseconds,
clearTime,
prevYear,
nextYear,
prevMonth,
nextMonth,
changeYearMonthAndClampDate,
extractDateFormat,
extractTimeFormat,
timeWithinRange,
} from "element-ui/src/utils/date-util";
import Clickoutside from "element-ui/src/utils/clickoutside";
import Locale from "element-ui/src/mixins/locale";
import ElInput from "element-ui/packages/input";
import ElButton from "element-ui/packages/button";
import TimePicker from "./time";
import YearTable from "../basic/year-table";
import MonthTable from "../basic/month-table";
import DateTable from "../basic/date-table";
export default {
mixins: [Locale],
directives: { Clickoutside },
watch: {
showTime(val) {
/* istanbul ignore if */
if (!val) return;
this.$nextTick((_) => {
const inputElm = this.$refs.input.$el;
if (inputElm) {
this.pickerWidth = inputElm.getBoundingClientRect().width + 10;
}
});
},
value(val) {
if (this.selectionMode === "dates" && this.value) return;
if (this.selectionMode === "months" && this.value) return;
if (this.selectionMode === "years" && this.value) return;
if (isDate(val)) {
this.date = new Date(val);
} else {
this.date = this.getDefaultValue();
}
},
defaultValue(val) {
if (!isDate(this.value)) {
this.date = val ? new Date(val) : new Date();
}
},
timePickerVisible(val) {
if (val) this.$nextTick(() => this.$refs.timepicker.adjustSpinners());
},
selectionMode(newVal) {
if (newVal === "month") {
/* istanbul ignore next */
if (this.currentView !== "year" || this.currentView !== "month") {
this.currentView = "month";
}
} else if (newVal === "dates") {
this.currentView = "date";
} else if (newVal === "years") {
this.currentView = "year";
} else if (newVal === "months") {
this.currentView = "month";
}
},
},
methods: {
proxyTimePickerDataProperties() {
const format = (timeFormat) => {
this.$refs.timepicker.format = timeFormat;
};
const value = (value) => {
this.$refs.timepicker.value = value;
};
const date = (date) => {
this.$refs.timepicker.date = date;
};
const selectableRange = (selectableRange) => {
this.$refs.timepicker.selectableRange = selectableRange;
};
this.$watch("value", value);
this.$watch("date", date);
this.$watch("selectableRange", selectableRange);
format(this.timeFormat);
value(this.value);
date(this.date);
selectableRange(this.selectableRange);
},
handleClear() {
this.date = this.getDefaultValue();
this.$emit("pick", null);
},
emit(value, ...args) {
if (!value) {
this.$emit("pick", value, ...args);
} else if (Array.isArray(value)) {
const dates = value.map((date) =>
this.showTime ? clearMilliseconds(date) : clearTime(date)
);
this.$emit("pick", dates, ...args);
} else {
this.$emit(
"pick",
this.showTime ? clearMilliseconds(value) : clearTime(value),
...args
);
}
this.userInputDate = null;
this.userInputTime = null;
},
// resetDate() {
// this.date = new Date(this.date);
// },
showMonthPicker() {
this.currentView = "month";
},
showYearPicker() {
this.currentView = "year";
},
// XXX: 没用到
// handleLabelClick() {
// if (this.currentView === 'date') {
// this.showMonthPicker();
// } else if (this.currentView === 'month') {
// this.showYearPicker();
// }
// },
prevMonth() {
this.date = prevMonth(this.date);
},
nextMonth() {
this.date = nextMonth(this.date);
},
prevYear() {
if (this.currentView === "year") {
this.date = prevYear(this.date, 10);
} else {
this.date = prevYear(this.date);
}
},
nextYear() {
if (this.currentView === "year") {
this.date = nextYear(this.date, 10);
} else {
this.date = nextYear(this.date);
}
},
handleShortcutClick(shortcut) {
if (shortcut.onClick) {
shortcut.onClick(this);
}
},
handleTimePick(value, visible, first) {
if (isDate(value)) {
const newDate = this.value
? modifyTime(
this.value,
value.getHours(),
value.getMinutes(),
value.getSeconds()
)
: modifyWithTimeString(this.getDefaultValue(), this.defaultTime);
this.date = newDate;
this.emit(this.date, true);
} else {
this.emit(value, true);
}
if (!first) {
this.timePickerVisible = visible;
}
},
handleTimePickClose() {
this.timePickerVisible = false;
},
handleMonthPick(month) {
if (this.selectionMode === "month") {
this.date = modifyDate(this.date, this.year, month, 1);
this.emit(this.date);
} else if (this.selectionMode === "months") {
this.emit(month, true);
} else {
this.date = changeYearMonthAndClampDate(this.date, this.year, month);
// TODO: should emit intermediate value ??
// this.emit(this.date);
this.currentView = "date";
}
},
handleDatePick(value) {
if (this.selectionMode === "day") {
let newDate = this.value
? modifyDate(
this.value,
value.getFullYear(),
value.getMonth(),
value.getDate()
)
: modifyWithTimeString(value, this.defaultTime);
// change default time while out of selectableRange
if (!this.checkDateWithinRange(newDate)) {
newDate = modifyDate(
this.selectableRange[0][0],
value.getFullYear(),
value.getMonth(),
value.getDate()
);
}
this.date = newDate;
this.emit(this.date, this.showTime);
} else if (this.selectionMode === "week") {
this.emit(value.date);
} else if (this.selectionMode === "dates") {
this.emit(value, true); // set false to keep panel open
}
},
handleYearPick(year) {
if (this.selectionMode === "year") {
this.date = modifyDate(this.date, year, 0, 1);
this.emit(this.date);
} else if (this.selectionMode === "years") {
this.emit(year, true);
} else {
this.date = changeYearMonthAndClampDate(this.date, year, this.month);
// TODO: should emit intermediate value ??
// this.emit(this.date, true);
this.currentView = "month";
}
},
changeToNow() {
// NOTE: not a permanent solution
// consider disable "now" button in the future
if (
(!this.disabledDate || !this.disabledDate(new Date())) &&
this.checkDateWithinRange(new Date())
) {
this.date = new Date();
this.emit(this.date);
}
},
confirm() {
if (
this.selectionMode === "dates" ||
this.selectionMode === "months" ||
this.selectionMode === "years"
) {
this.emit(this.value);
} else {
// value were emitted in handle{Date,Time}Pick, nothing to update here
// deal with the scenario where: user opens the picker, then confirm without doing anything
const value = this.value
? this.value
: modifyWithTimeString(this.getDefaultValue(), this.defaultTime);
this.date = new Date(value); // refresh date
this.emit(value);
}
},
resetView() {
if (this.selectionMode === "month" || this.selectionMode === "months") {
this.currentView = "month";
} else if (
this.selectionMode === "year" ||
this.selectionMode === "years"
) {
this.currentView = "year";
} else {
this.currentView = "date";
}
},
handleEnter() {
document.body.addEventListener("keydown", this.handleKeydown);
},
handleLeave() {
this.$emit("dodestroy");
document.body.removeEventListener("keydown", this.handleKeydown);
},
handleKeydown(event) {
const keyCode = event.keyCode;
const list = [38, 40, 37, 39];
if (this.visible && !this.timePickerVisible) {
if (list.indexOf(keyCode) !== -1) {
this.handleKeyControl(keyCode);
event.stopPropagation();
event.preventDefault();
}
if (
keyCode === 13 &&
this.userInputDate === null &&
this.userInputTime === null
) {
// Enter
this.emit(this.date, false);
}
}
},
handleKeyControl(keyCode) {
const mapping = {
year: {
38: -4,
40: 4,
37: -1,
39: 1,
offset: (date, step) => date.setFullYear(date.getFullYear() + step),
},
month: {
38: -4,
40: 4,
37: -1,
39: 1,
offset: (date, step) => date.setMonth(date.getMonth() + step),
},
week: {
38: -1,
40: 1,
37: -1,
39: 1,
offset: (date, step) => date.setDate(date.getDate() + step * 7),
},
day: {
38: -7,
40: 7,
37: -1,
39: 1,
offset: (date, step) => date.setDate(date.getDate() + step),
},
};
const mode = this.selectionMode;
const year = 3.1536e10;
const now = this.date.getTime();
const newDate = new Date(this.date.getTime());
while (Math.abs(now - newDate.getTime()) <= year) {
const map = mapping[mode];
map.offset(newDate, map[keyCode]);
if (
typeof this.disabledDate === "function" &&
this.disabledDate(newDate)
) {
continue;
}
this.date = newDate;
this.$emit("pick", newDate, true);
break;
}
},
handleVisibleTimeChange(value) {
const time = parseDate(value, this.timeFormat);
if (time && this.checkDateWithinRange(time)) {
this.date = modifyDate(time, this.year, this.month, this.monthDate);
this.userInputTime = null;
this.$refs.timepicker.value = this.date;
this.timePickerVisible = false;
this.emit(this.date, true);
}
},
handleVisibleDateChange(value) {
const date = parseDate(value, this.dateFormat);
if (date) {
if (
typeof this.disabledDate === "function" &&
this.disabledDate(date)
) {
return;
}
this.date = modifyTime(
date,
this.date.getHours(),
this.date.getMinutes(),
this.date.getSeconds()
);
this.userInputDate = null;
this.resetView();
this.emit(this.date, true);
}
},
isValidValue(value) {
return (
value &&
!isNaN(value) &&
(typeof this.disabledDate === "function"
? !this.disabledDate(value)
: true) &&
this.checkDateWithinRange(value)
);
},
getDefaultValue() {
// if default-value is set, return it
// otherwise, return now (the moment this method gets called)
return this.defaultValue ? new Date(this.defaultValue) : new Date();
},
checkDateWithinRange(date) {
return this.selectableRange.length > 0
? timeWithinRange(date, this.selectableRange, this.format || "HH:mm:ss")
: true;
},
},
components: {
TimePicker,
YearTable,
MonthTable,
DateTable,
ElInput,
ElButton,
},
data() {
return {
popperClass: "",
date: new Date(),
value: "",
defaultValue: null, // use getDefaultValue() for time computation
defaultTime: null,
showTime: false,
selectionMode: "day",
shortcuts: "",
visible: true,
currentView: "date",
disabledDate: "",
cellClassName: "",
selectableRange: [],
firstDayOfWeek: 7,
showWeekNumber: false,
timePickerVisible: false,
format: "",
arrowControl: false,
userInputDate: null,
userInputTime: null,
};
},
computed: {
year() {
return this.date.getFullYear();
},
month() {
return this.date.getMonth();
},
week() {
return getWeekNumber(this.date);
},
monthDate() {
return this.date.getDate();
},
footerVisible() {
return (
this.showTime ||
this.selectionMode === "dates" ||
this.selectionMode === "months" ||
this.selectionMode === "years"
);
},
visibleTime() {
if (this.userInputTime !== null) {
return this.userInputTime;
} else {
return formatDate(this.value || this.defaultValue, this.timeFormat);
}
},
visibleDate() {
if (this.userInputDate !== null) {
return this.userInputDate;
} else {
return formatDate(this.value || this.defaultValue, this.dateFormat);
}
},
yearLabel() {
const yearTranslation = this.t("el.datepicker.year");
if (this.currentView === "year") {
const startYear = Math.floor(this.year / 10) * 10;
if (yearTranslation) {
return (
startYear +
" " +
yearTranslation +
" - " +
(startYear + 9) +
" " +
yearTranslation
);
}
return startYear + " - " + (startYear + 9);
}
return this.year + " " + yearTranslation;
},
timeFormat() {
if (this.format) {
return extractTimeFormat(this.format);
} else {
return "HH:mm:ss";
}
},
dateFormat() {
if (this.format) {
return extractDateFormat(this.format);
} else {
return "yyyy-MM-dd";
}
},
},
};
</script>
<template>
<transition name="el-zoom-in-top" @after-leave="$emit('dodestroy')">
<div
v-show="visible"
class="el-picker-panel el-date-range-picker el-popper"
:class="[
{
'has-sidebar': $slots.sidebar || shortcuts,
},
popperClass,
]"
>
<div class="el-picker-panel__body-wrapper">
<slot name="sidebar" class="el-picker-panel__sidebar"></slot>
<div class="el-picker-panel__sidebar" v-if="shortcuts">
<button
type="button"
class="el-picker-panel__shortcut"
v-for="(shortcut, key) in shortcuts"
:key="key"
@click="handleShortcutClick(shortcut)"
>
{{ shortcut.text }}
</button>
</div>
<div class="el-picker-panel__body">
<div
class="el-picker-panel__content el-date-range-picker__content is-left"
>
<div class="el-date-range-picker__header">
<button
type="button"
@click="leftPrevYear"
class="el-picker-panel__icon-btn el-icon-d-arrow-left"
></button>
<button
type="button"
v-if="unlinkPanels"
@click="leftNextYear"
:disabled="!enableYearArrow"
:class="{ 'is-disabled': !enableYearArrow }"
class="el-picker-panel__icon-btn el-icon-d-arrow-right"
></button>
<div>{{ leftLabel }}</div>
</div>
<month-table
selection-mode="range"
:date="leftDate"
:default-value="defaultValue"
:min-date="minDate"
:max-date="maxDate"
:range-state="rangeState"
:disabled-date="disabledDate"
@changerange="handleChangeRange"
@pick="handleRangePick"
>
</month-table>
</div>
<div
class="el-picker-panel__content el-date-range-picker__content is-right"
>
<div class="el-date-range-picker__header">
<button
type="button"
v-if="unlinkPanels"
@click="rightPrevYear"
:disabled="!enableYearArrow"
:class="{ 'is-disabled': !enableYearArrow }"
class="el-picker-panel__icon-btn el-icon-d-arrow-left"
></button>
<button
type="button"
@click="rightNextYear"
class="el-picker-panel__icon-btn el-icon-d-arrow-right"
></button>
<div>{{ rightLabel }}</div>
</div>
<month-table
selection-mode="range"
:date="rightDate"
:default-value="defaultValue"
:min-date="minDate"
:max-date="maxDate"
:range-state="rangeState"
:disabled-date="disabledDate"
@changerange="handleChangeRange"
@pick="handleRangePick"
>
</month-table>
</div>
</div>
</div>
</div>
</transition>
</template>
<script type="text/babel">
/* eslint-disable */
import {
isDate,
modifyWithTimeString,
prevYear,
nextYear,
nextMonth,
} from "element-ui/src/utils/date-util";
import Clickoutside from "element-ui/src/utils/clickoutside";
import Locale from "element-ui/src/mixins/locale";
import MonthTable from "../basic/month-table";
import ElInput from "element-ui/packages/input";
import ElButton from "element-ui/packages/button";
const calcDefaultValue = (defaultValue) => {
if (Array.isArray(defaultValue)) {
return [new Date(defaultValue[0]), new Date(defaultValue[1])];
} else if (defaultValue) {
return [new Date(defaultValue), nextMonth(new Date(defaultValue))];
} else {
return [new Date(), nextMonth(new Date())];
}
};
export default {
mixins: [Locale],
directives: { Clickoutside },
computed: {
btnDisabled() {
return !(
this.minDate &&
this.maxDate &&
!this.selecting &&
this.isValidValue([this.minDate, this.maxDate])
);
},
leftLabel() {
return this.leftDate.getFullYear() + " " + this.t("el.datepicker.year");
},
rightLabel() {
return this.rightDate.getFullYear() + " " + this.t("el.datepicker.year");
},
leftYear() {
return this.leftDate.getFullYear();
},
rightYear() {
return this.rightDate.getFullYear() === this.leftDate.getFullYear()
? this.leftDate.getFullYear() + 1
: this.rightDate.getFullYear();
},
enableYearArrow() {
return this.unlinkPanels && this.rightYear > this.leftYear + 1;
},
},
data() {
return {
popperClass: "",
value: [],
defaultValue: null,
defaultTime: null,
minDate: "",
maxDate: "",
leftDate: new Date(),
rightDate: nextYear(new Date()),
rangeState: {
endDate: null,
selecting: false,
row: null,
column: null,
},
shortcuts: "",
visible: "",
disabledDate: "",
format: "",
arrowControl: false,
unlinkPanels: false,
};
},
watch: {
value(newVal) {
if (!newVal) {
this.minDate = null;
this.maxDate = null;
} else if (Array.isArray(newVal)) {
this.minDate = isDate(newVal[0]) ? new Date(newVal[0]) : null;
this.maxDate = isDate(newVal[1]) ? new Date(newVal[1]) : null;
if (this.minDate) {
this.leftDate = this.minDate;
if (this.unlinkPanels && this.maxDate) {
const minDateYear = this.minDate.getFullYear();
const maxDateYear = this.maxDate.getFullYear();
this.rightDate =
minDateYear === maxDateYear
? nextYear(this.maxDate)
: this.maxDate;
} else {
this.rightDate = nextYear(this.leftDate);
}
} else {
this.leftDate = calcDefaultValue(this.defaultValue)[0];
this.rightDate = nextYear(this.leftDate);
}
}
},
defaultValue(val) {
if (!Array.isArray(this.value)) {
const [left, right] = calcDefaultValue(val);
this.leftDate = left;
this.rightDate =
val &&
val[1] &&
left.getFullYear() !== right.getFullYear() &&
this.unlinkPanels
? right
: nextYear(this.leftDate);
}
},
},
methods: {
handleClear() {
this.minDate = null;
this.maxDate = null;
this.leftDate = calcDefaultValue(this.defaultValue)[0];
this.rightDate = nextYear(this.leftDate);
this.$emit("pick", null);
},
handleChangeRange(val) {
this.minDate = val.minDate;
this.maxDate = val.maxDate;
this.rangeState = val.rangeState;
},
handleRangePick(val, close = true) {
const defaultTime = this.defaultTime || [];
const minDate = modifyWithTimeString(val.minDate, defaultTime[0]);
const maxDate = modifyWithTimeString(val.maxDate, defaultTime[1]);
if (this.maxDate === maxDate && this.minDate === minDate) {
return;
}
this.onPick && this.onPick(val);
this.maxDate = maxDate;
this.minDate = minDate;
// workaround for https://github.com/ElemeFE/element/issues/7539, should remove this block when we don't have to care about Chromium 55 - 57
setTimeout(() => {
this.maxDate = maxDate;
this.minDate = minDate;
}, 10);
if (!close) return;
this.handleConfirm();
},
handleShortcutClick(shortcut) {
if (shortcut.onClick) {
shortcut.onClick(this);
}
},
// leftPrev*, rightNext* need to take care of `unlinkPanels`
leftPrevYear() {
this.leftDate = prevYear(this.leftDate);
if (!this.unlinkPanels) {
this.rightDate = prevYear(this.rightDate);
}
},
rightNextYear() {
if (!this.unlinkPanels) {
this.leftDate = nextYear(this.leftDate);
}
this.rightDate = nextYear(this.rightDate);
},
// leftNext*, rightPrev* are called when `unlinkPanels` is true
leftNextYear() {
this.leftDate = nextYear(this.leftDate);
},
rightPrevYear() {
this.rightDate = prevYear(this.rightDate);
},
handleConfirm(visible = false) {
if (this.isValidValue([this.minDate, this.maxDate])) {
this.$emit("pick", [this.minDate, this.maxDate], visible);
}
},
isValidValue(value) {
return (
Array.isArray(value) &&
value &&
value[0] &&
value[1] &&
isDate(value[0]) &&
isDate(value[1]) &&
value[0].getTime() <= value[1].getTime() &&
(typeof this.disabledDate === "function"
? !this.disabledDate(value[0]) && !this.disabledDate(value[1])
: true)
);
},
resetView() {
// NOTE: this is a hack to reset {min, max}Date on picker open.
// TODO: correct way of doing so is to refactor {min, max}Date to be dependent on value and internal selection state
// an alternative would be resetView whenever picker becomes visible, should also investigate date-panel's resetView
this.minDate =
this.value && isDate(this.value[0]) ? new Date(this.value[0]) : null;
this.maxDate =
this.value && isDate(this.value[0]) ? new Date(this.value[1]) : null;
},
},
components: { MonthTable, ElInput, ElButton },
};
</script>
<template>
<transition name="el-zoom-in-top" @after-leave="$emit('dodestroy')">
<div
v-show="visible"
class="el-time-range-picker el-picker-panel el-popper"
:class="popperClass"
>
<div class="el-time-range-picker__content">
<div class="el-time-range-picker__cell">
<div class="el-time-range-picker__header">
{{ t("el.datepicker.startTime") }}
</div>
<div
:class="{ 'has-seconds': showSeconds, 'is-arrow': arrowControl }"
class="el-time-range-picker__body el-time-panel__content"
>
<time-spinner
ref="minSpinner"
:show-seconds="showSeconds"
:am-pm-mode="amPmMode"
@change="handleMinChange"
:arrow-control="arrowControl"
@select-range="setMinSelectionRange"
:date="minDate"
>
</time-spinner>
</div>
</div>
<div class="el-time-range-picker__cell">
<div class="el-time-range-picker__header">
{{ t("el.datepicker.endTime") }}
</div>
<div
:class="{ 'has-seconds': showSeconds, 'is-arrow': arrowControl }"
class="el-time-range-picker__body el-time-panel__content"
>
<time-spinner
ref="maxSpinner"
:show-seconds="showSeconds"
:am-pm-mode="amPmMode"
@change="handleMaxChange"
:arrow-control="arrowControl"
@select-range="setMaxSelectionRange"
:date="maxDate"
>
</time-spinner>
</div>
</div>
</div>
<div class="el-time-panel__footer">
<button
type="button"
class="el-time-panel__btn cancel"
@click="handleCancel()"
>
{{ t("el.datepicker.cancel") }}
</button>
<button
type="button"
class="el-time-panel__btn confirm"
@click="handleConfirm()"
:disabled="btnDisabled"
>
{{ t("el.datepicker.confirm") }}
</button>
</div>
</div>
</transition>
</template>
<script type="text/babel">
/* eslint-disable */
import {
parseDate,
limitTimeRange,
modifyDate,
clearMilliseconds,
timeWithinRange,
} from "element-ui/src/utils/date-util";
import Locale from "element-ui/src/mixins/locale";
import TimeSpinner from "../basic/time-spinner";
const MIN_TIME = parseDate("00:00:00", "HH:mm:ss");
const MAX_TIME = parseDate("23:59:59", "HH:mm:ss");
const minTimeOfDay = function (date) {
return modifyDate(
MIN_TIME,
date.getFullYear(),
date.getMonth(),
date.getDate()
);
};
const maxTimeOfDay = function (date) {
return modifyDate(
MAX_TIME,
date.getFullYear(),
date.getMonth(),
date.getDate()
);
};
// increase time by amount of milliseconds, but within the range of day
const advanceTime = function (date, amount) {
return new Date(
Math.min(date.getTime() + amount, maxTimeOfDay(date).getTime())
);
};
export default {
mixins: [Locale],
components: { TimeSpinner },
computed: {
showSeconds() {
return (this.format || "").indexOf("ss") !== -1;
},
offset() {
return this.showSeconds ? 11 : 8;
},
spinner() {
return this.selectionRange[0] < this.offset
? this.$refs.minSpinner
: this.$refs.maxSpinner;
},
btnDisabled() {
return this.minDate.getTime() > this.maxDate.getTime();
},
amPmMode() {
if ((this.format || "").indexOf("A") !== -1) return "A";
if ((this.format || "").indexOf("a") !== -1) return "a";
return "";
},
},
data() {
return {
popperClass: "",
minDate: new Date(),
maxDate: new Date(),
value: [],
oldValue: [new Date(), new Date()],
defaultValue: null,
format: "HH:mm:ss",
visible: false,
selectionRange: [0, 2],
arrowControl: false,
};
},
watch: {
value(value) {
if (Array.isArray(value)) {
this.minDate = new Date(value[0]);
this.maxDate = new Date(value[1]);
} else {
if (Array.isArray(this.defaultValue)) {
this.minDate = new Date(this.defaultValue[0]);
this.maxDate = new Date(this.defaultValue[1]);
} else if (this.defaultValue) {
this.minDate = new Date(this.defaultValue);
this.maxDate = advanceTime(
new Date(this.defaultValue),
60 * 60 * 1000
);
} else {
this.minDate = new Date();
this.maxDate = advanceTime(new Date(), 60 * 60 * 1000);
}
}
},
visible(val) {
if (val) {
this.oldValue = this.value;
this.$nextTick(() => this.$refs.minSpinner.emitSelectRange("hours"));
}
},
},
methods: {
handleClear() {
this.$emit("pick", null);
},
handleCancel() {
this.$emit("pick", this.oldValue);
},
handleMinChange(date) {
this.minDate = clearMilliseconds(date);
this.handleChange();
},
handleMaxChange(date) {
this.maxDate = clearMilliseconds(date);
this.handleChange();
},
handleChange() {
if (this.isValidValue([this.minDate, this.maxDate])) {
this.$refs.minSpinner.selectableRange = [
[minTimeOfDay(this.minDate), this.maxDate],
];
this.$refs.maxSpinner.selectableRange = [
[this.minDate, maxTimeOfDay(this.maxDate)],
];
this.$emit("pick", [this.minDate, this.maxDate], true);
}
},
setMinSelectionRange(start, end) {
this.$emit("select-range", start, end, "min");
this.selectionRange = [start, end];
},
setMaxSelectionRange(start, end) {
this.$emit("select-range", start, end, "max");
this.selectionRange = [start + this.offset, end + this.offset];
},
handleConfirm(visible = false) {
const minSelectableRange = this.$refs.minSpinner.selectableRange;
const maxSelectableRange = this.$refs.maxSpinner.selectableRange;
this.minDate = limitTimeRange(
this.minDate,
minSelectableRange,
this.format
);
this.maxDate = limitTimeRange(
this.maxDate,
maxSelectableRange,
this.format
);
this.$emit("pick", [this.minDate, this.maxDate], visible);
},
adjustSpinners() {
this.$refs.minSpinner.adjustSpinners();
this.$refs.maxSpinner.adjustSpinners();
},
changeSelectionRange(step) {
const list = this.showSeconds ? [0, 3, 6, 11, 14, 17] : [0, 3, 8, 11];
const mapping = ["hours", "minutes"].concat(
this.showSeconds ? ["seconds"] : []
);
const index = list.indexOf(this.selectionRange[0]);
const next = (index + step + list.length) % list.length;
const half = list.length / 2;
if (next < half) {
this.$refs.minSpinner.emitSelectRange(mapping[next]);
} else {
this.$refs.maxSpinner.emitSelectRange(mapping[next - half]);
}
},
isValidValue(date) {
return (
Array.isArray(date) &&
timeWithinRange(this.minDate, this.$refs.minSpinner.selectableRange) &&
timeWithinRange(this.maxDate, this.$refs.maxSpinner.selectableRange)
);
},
handleKeydown(event) {
const keyCode = event.keyCode;
const mapping = { 38: -1, 40: 1, 37: -1, 39: 1 };
// Left or Right
if (keyCode === 37 || keyCode === 39) {
const step = mapping[keyCode];
this.changeSelectionRange(step);
event.preventDefault();
return;
}
// Up or Down
if (keyCode === 38 || keyCode === 40) {
const step = mapping[keyCode];
this.spinner.scrollDown(step);
event.preventDefault();
return;
}
},
},
};
</script>
<template>
<transition
name="el-zoom-in-top"
@before-enter="handleMenuEnter"
@after-leave="$emit('dodestroy')"
>
<div
ref="popper"
v-show="visible"
:style="{ width: width + 'px' }"
:class="popperClass"
class="el-picker-panel time-select el-popper"
>
<el-scrollbar noresize wrap-class="el-picker-panel__content">
<div
class="time-select-item"
v-for="item in items"
:class="{
selected: value === item.value,
disabled: item.disabled,
default: item.value === defaultValue,
}"
:disabled="item.disabled"
:key="item.value"
@click="handleClick(item)"
>
{{ item.value }}
</div>
</el-scrollbar>
</div>
</transition>
</template>
<script type="text/babel">
/* eslint-disable */
import ElScrollbar from "element-ui/packages/scrollbar";
import scrollIntoView from "element-ui/src/utils/scroll-into-view";
const parseTime = function (time) {
const values = (time || "").split(":");
if (values.length >= 2) {
const hours = parseInt(values[0], 10);
const minutes = parseInt(values[1], 10);
return {
hours,
minutes,
};
}
/* istanbul ignore next */
return null;
};
const compareTime = function (time1, time2) {
const value1 = parseTime(time1);
const value2 = parseTime(time2);
const minutes1 = value1.minutes + value1.hours * 60;
const minutes2 = value2.minutes + value2.hours * 60;
if (minutes1 === minutes2) {
return 0;
}
return minutes1 > minutes2 ? 1 : -1;
};
const formatTime = function (time) {
return (
(time.hours < 10 ? "0" + time.hours : time.hours) +
":" +
(time.minutes < 10 ? "0" + time.minutes : time.minutes)
);
};
const nextTime = function (time, step) {
const timeValue = parseTime(time);
const stepValue = parseTime(step);
const next = {
hours: timeValue.hours,
minutes: timeValue.minutes,
};
next.minutes += stepValue.minutes;
next.hours += stepValue.hours;
next.hours += Math.floor(next.minutes / 60);
next.minutes = next.minutes % 60;
return formatTime(next);
};
export default {
components: { ElScrollbar },
watch: {
value(val) {
if (!val) return;
this.$nextTick(() => this.scrollToOption());
},
},
methods: {
handleClick(item) {
if (!item.disabled) {
this.$emit("pick", item.value);
}
},
handleClear() {
this.$emit("pick", null);
},
scrollToOption(selector = ".selected") {
const menu = this.$refs.popper.querySelector(".el-picker-panel__content");
scrollIntoView(menu, menu.querySelector(selector));
},
handleMenuEnter() {
const selected =
this.items.map((item) => item.value).indexOf(this.value) !== -1;
const hasDefault =
this.items.map((item) => item.value).indexOf(this.defaultValue) !== -1;
const option =
(selected && ".selected") ||
(hasDefault && ".default") ||
".time-select-item:not(.disabled)";
this.$nextTick(() => this.scrollToOption(option));
},
scrollDown(step) {
const items = this.items;
const length = items.length;
let total = items.length;
let index = items.map((item) => item.value).indexOf(this.value);
while (total--) {
index = (index + step + length) % length;
if (!items[index].disabled) {
this.$emit("pick", items[index].value, true);
return;
}
}
},
isValidValue(date) {
return (
this.items
.filter((item) => !item.disabled)
.map((item) => item.value)
.indexOf(date) !== -1
);
},
handleKeydown(event) {
const keyCode = event.keyCode;
if (keyCode === 38 || keyCode === 40) {
const mapping = { 40: 1, 38: -1 };
const offset = mapping[keyCode.toString()];
this.scrollDown(offset);
event.stopPropagation();
return;
}
},
},
data() {
return {
popperClass: "",
start: "09:00",
end: "18:00",
step: "00:30",
value: "",
defaultValue: "",
visible: false,
minTime: "",
maxTime: "",
width: 0,
};
},
computed: {
items() {
const start = this.start;
const end = this.end;
const step = this.step;
const result = [];
if (start && end && step) {
let current = start;
while (compareTime(current, end) <= 0) {
result.push({
value: current,
disabled:
compareTime(current, this.minTime || "-1:-1") <= 0 ||
compareTime(current, this.maxTime || "100:100") >= 0,
});
current = nextTime(current, step);
}
}
return result;
},
},
};
</script>
<template>
<transition name="el-zoom-in-top" @after-leave="$emit('dodestroy')">
<div v-show="visible" class="el-time-panel el-popper" :class="popperClass">
<div
class="el-time-panel__content"
:class="{ 'has-seconds': showSeconds }"
>
<time-spinner
ref="spinner"
@change="handleChange"
:arrow-control="useArrow"
:show-seconds="showSeconds"
:am-pm-mode="amPmMode"
@select-range="setSelectionRange"
:date="date"
>
</time-spinner>
</div>
<div class="el-time-panel__footer">
<button
type="button"
class="el-time-panel__btn cancel"
@click="handleCancel"
>
{{ t("el.datepicker.cancel") }}
</button>
<button
type="button"
class="el-time-panel__btn"
:class="{ confirm: !disabled }"
@click="handleConfirm()"
>
{{ t("el.datepicker.confirm") }}
</button>
</div>
</div>
</transition>
</template>
<script type="text/babel">
/* eslint-disable */
import {
limitTimeRange,
isDate,
clearMilliseconds,
timeWithinRange,
} from "element-ui/src/utils/date-util";
import Locale from "element-ui/src/mixins/locale";
import TimeSpinner from "../basic/time-spinner";
export default {
mixins: [Locale],
components: {
TimeSpinner,
},
props: {
visible: Boolean,
timeArrowControl: Boolean,
},
watch: {
visible(val) {
if (val) {
this.oldValue = this.value;
this.$nextTick(() => this.$refs.spinner.emitSelectRange("hours"));
} else {
this.needInitAdjust = true;
}
},
value(newVal) {
let date;
if (newVal instanceof Date) {
date = limitTimeRange(newVal, this.selectableRange, this.format);
} else if (!newVal) {
date = this.defaultValue ? new Date(this.defaultValue) : new Date();
}
this.date = date;
if (this.visible && this.needInitAdjust) {
this.$nextTick((_) => this.adjustSpinners());
this.needInitAdjust = false;
}
},
selectableRange(val) {
this.$refs.spinner.selectableRange = val;
},
defaultValue(val) {
if (!isDate(this.value)) {
this.date = val ? new Date(val) : new Date();
}
},
},
data() {
return {
popperClass: "",
format: "HH:mm:ss",
value: "",
defaultValue: null,
date: new Date(),
oldValue: new Date(),
selectableRange: [],
selectionRange: [0, 2],
disabled: false,
arrowControl: false,
needInitAdjust: true,
};
},
computed: {
showSeconds() {
return (this.format || "").indexOf("ss") !== -1;
},
useArrow() {
return this.arrowControl || this.timeArrowControl || false;
},
amPmMode() {
if ((this.format || "").indexOf("A") !== -1) return "A";
if ((this.format || "").indexOf("a") !== -1) return "a";
return "";
},
},
methods: {
handleCancel() {
this.$emit("pick", this.oldValue, false);
},
handleChange(date) {
// this.visible avoids edge cases, when use scrolls during panel closing animation
if (this.visible) {
this.date = clearMilliseconds(date);
// if date is out of range, do not emit
if (this.isValidValue(this.date)) {
this.$emit("pick", this.date, true);
}
}
},
setSelectionRange(start, end) {
this.$emit("select-range", start, end);
this.selectionRange = [start, end];
},
handleConfirm(visible = false, first) {
if (first) return;
const date = clearMilliseconds(
limitTimeRange(this.date, this.selectableRange, this.format)
);
this.$emit("pick", date, visible, first);
},
handleKeydown(event) {
const keyCode = event.keyCode;
const mapping = { 38: -1, 40: 1, 37: -1, 39: 1 };
// Left or Right
if (keyCode === 37 || keyCode === 39) {
const step = mapping[keyCode];
this.changeSelectionRange(step);
event.preventDefault();
return;
}
// Up or Down
if (keyCode === 38 || keyCode === 40) {
const step = mapping[keyCode];
this.$refs.spinner.scrollDown(step);
event.preventDefault();
return;
}
},
isValidValue(date) {
return timeWithinRange(date, this.selectableRange, this.format);
},
adjustSpinners() {
return this.$refs.spinner.adjustSpinners();
},
changeSelectionRange(step) {
const list = [0, 3].concat(this.showSeconds ? [6] : []);
const mapping = ["hours", "minutes"].concat(
this.showSeconds ? ["seconds"] : []
);
const index = list.indexOf(this.selectionRange[0]);
const next = (index + step + list.length) % list.length;
this.$refs.spinner.emitSelectRange(mapping[next]);
},
},
mounted() {
this.$nextTick(() => this.handleConfirm(true, true));
this.$emit("mounted");
},
};
</script>
<template>
<div class="w-full" ref="reference"></div>
</template>
<script>
/* eslint-disable */
import Vue from "vue";
import Clickoutside from "element-ui/src/utils/clickoutside";
import {
formatDate,
parseDate,
isDateObject,
getWeekNumber,
} from "element-ui/src/utils/date-util";
// import Popper from "element-ui/src/utils/vue-popper";
import Emitter from "element-ui/src/mixins/emitter";
import ElInput from "element-ui/packages/input";
// import merge from "element-ui/src/utils/merge";
// const NewPopper = {
// props: {
// appendToBody: Popper.props.appendToBody,
// offset: Popper.props.offset,
// boundariesPadding: Popper.props.boundariesPadding,
// arrowOffset: Popper.props.arrowOffset,
// transformOrigin: Popper.props.transformOrigin,
// },
// methods: Popper.methods,
// data() {
// return merge({ visibleArrow: true }, Popper.data);
// },
// beforeDestroy: Popper.beforeDestroy,
// };
const DEFAULT_FORMATS = {
date: "yyyy-MM-dd",
month: "yyyy-MM",
months: "yyyy-MM",
datetime: "yyyy-MM-dd HH:mm:ss",
time: "HH:mm:ss",
week: "yyyywWW",
timerange: "HH:mm:ss",
daterange: "yyyy-MM-dd",
monthrange: "yyyy-MM",
datetimerange: "yyyy-MM-dd HH:mm:ss",
year: "yyyy",
years: "yyyy",
};
const HAVE_TRIGGER_TYPES = [
"date",
"datetime",
"time",
"time-select",
"week",
"month",
"year",
"daterange",
"monthrange",
"timerange",
"datetimerange",
"dates",
"months",
"years",
];
const DATE_FORMATTER = function (value, format) {
if (format === "timestamp") return value.getTime();
return formatDate(value, format);
};
const DATE_PARSER = function (text, format) {
if (format === "timestamp") return new Date(Number(text));
return parseDate(text, format);
};
const RANGE_FORMATTER = function (value, format) {
if (Array.isArray(value) && value.length === 2) {
const start = value[0];
const end = value[1];
if (start && end) {
return [DATE_FORMATTER(start, format), DATE_FORMATTER(end, format)];
}
}
return "";
};
const RANGE_PARSER = function (array, format, separator) {
if (!Array.isArray(array)) {
array = array.split(separator);
}
if (array.length === 2) {
const range1 = array[0];
const range2 = array[1];
return [DATE_PARSER(range1, format), DATE_PARSER(range2, format)];
}
return [];
};
const TYPE_VALUE_RESOLVER_MAP = {
default: {
formatter(value) {
if (!value) return "";
return "" + value;
},
parser(text) {
if (text === undefined || text === "") return null;
return text;
},
},
week: {
formatter(value, format) {
let week = getWeekNumber(value);
let month = value.getMonth();
const trueDate = new Date(value);
if (week === 1 && month === 11) {
trueDate.setHours(0, 0, 0, 0);
trueDate.setDate(
trueDate.getDate() + 3 - ((trueDate.getDay() + 6) % 7)
);
}
let date = formatDate(trueDate, format);
date = /WW/.test(date)
? date.replace(/WW/, week < 10 ? "0" + week : week)
: date.replace(/W/, week);
return date;
},
parser(text, format) {
// parse as if a normal date
return TYPE_VALUE_RESOLVER_MAP.date.parser(text, format);
},
},
date: {
formatter: DATE_FORMATTER,
parser: DATE_PARSER,
},
datetime: {
formatter: DATE_FORMATTER,
parser: DATE_PARSER,
},
daterange: {
formatter: RANGE_FORMATTER,
parser: RANGE_PARSER,
},
monthrange: {
formatter: RANGE_FORMATTER,
parser: RANGE_PARSER,
},
datetimerange: {
formatter: RANGE_FORMATTER,
parser: RANGE_PARSER,
},
timerange: {
formatter: RANGE_FORMATTER,
parser: RANGE_PARSER,
},
time: {
formatter: DATE_FORMATTER,
parser: DATE_PARSER,
},
month: {
formatter: DATE_FORMATTER,
parser: DATE_PARSER,
},
year: {
formatter: DATE_FORMATTER,
parser: DATE_PARSER,
},
number: {
formatter(value) {
if (!value) return "";
return "" + value;
},
parser(text) {
let result = Number(text);
if (!isNaN(text)) {
return result;
} else {
return null;
}
},
},
dates: {
formatter(value, format) {
return value.map((date) => DATE_FORMATTER(date, format));
},
parser(value, format) {
return (typeof value === "string" ? value.split(", ") : value).map(
(date) => (date instanceof Date ? date : DATE_PARSER(date, format))
);
},
},
months: {
formatter(value, format) {
return value.map((date) => DATE_FORMATTER(date, format));
},
parser(value, format) {
return (typeof value === "string" ? value.split(", ") : value).map(
(date) => (date instanceof Date ? date : DATE_PARSER(date, format))
);
},
},
years: {
formatter(value, format) {
return value.map((date) => DATE_FORMATTER(date, format));
},
parser(value, format) {
return (typeof value === "string" ? value.split(", ") : value).map(
(date) => (date instanceof Date ? date : DATE_PARSER(date, format))
);
},
},
};
const PLACEMENT_MAP = {
left: "bottom-start",
center: "bottom",
right: "bottom-end",
};
const parseAsFormatAndType = (
value,
customFormat,
type,
rangeSeparator = "-"
) => {
if (!value) return null;
const parser = (
TYPE_VALUE_RESOLVER_MAP[type] || TYPE_VALUE_RESOLVER_MAP["default"]
).parser;
const format = customFormat || DEFAULT_FORMATS[type];
return parser(value, format, rangeSeparator);
};
const formatAsFormatAndType = (value, customFormat, type) => {
if (!value) return null;
const formatter = (
TYPE_VALUE_RESOLVER_MAP[type] || TYPE_VALUE_RESOLVER_MAP["default"]
).formatter;
const format = customFormat || DEFAULT_FORMATS[type];
return formatter(value, format);
};
/*
* Considers:
* 1. Date object
* 2. date string
* 3. array of 1 or 2
*/
const valueEquals = function (a, b) {
// considers Date object and string
const dateEquals = function (a, b) {
const aIsDate = a instanceof Date;
const bIsDate = b instanceof Date;
if (aIsDate && bIsDate) {
return a.getTime() === b.getTime();
}
if (!aIsDate && !bIsDate) {
return a === b;
}
return false;
};
const aIsArray = a instanceof Array;
const bIsArray = b instanceof Array;
if (aIsArray && bIsArray) {
if (a.length !== b.length) {
return false;
}
return a.every((item, index) => dateEquals(item, b[index]));
}
if (!aIsArray && !bIsArray) {
return dateEquals(a, b);
}
return false;
};
const isString = function (val) {
return typeof val === "string" || val instanceof String;
};
const validator = function (val) {
// either: String, Array of String, null / undefined
return (
val === null ||
val === undefined ||
isString(val) ||
(Array.isArray(val) && val.length === 2 && val.every(isString))
);
};
export default {
mixins: [Emitter],
inject: {
elForm: {
default: "",
},
elFormItem: {
default: "",
},
},
props: {
size: String,
format: String,
valueFormat: String,
readonly: Boolean,
placeholder: String,
startPlaceholder: String,
endPlaceholder: String,
prefixIcon: String,
clearIcon: {
type: String,
default: "el-icon-circle-close",
},
name: {
default: "",
validator,
},
disabled: Boolean,
clearable: {
type: Boolean,
default: true,
},
id: {
default: "",
validator,
},
popperClass: String,
editable: {
type: Boolean,
default: true,
},
align: {
type: String,
default: "left",
},
value: {},
defaultValue: {},
defaultTime: {},
rangeSeparator: {
default: "-",
},
pickerOptions: {},
unlinkPanels: Boolean,
validateEvent: {
type: Boolean,
default: true,
},
},
components: { ElInput },
directives: { Clickoutside },
data() {
return {
pickerVisible: false,
showClose: false,
userInput: null,
valueOnOpen: null, // value when picker opens, used to determine whether to emit change
unwatchPickerOptions: null,
};
},
watch: {
pickerVisible(val) {
if (this.readonly || this.pickerDisabled) return;
if (val) {
this.showPicker();
this.valueOnOpen = Array.isArray(this.value)
? [...this.value]
: this.value;
} else {
this.hidePicker();
this.emitChange(this.value);
this.userInput = null;
if (this.validateEvent) {
this.dispatch("ElFormItem", "el.form.blur");
}
this.$emit("blur", this);
this.blur();
}
},
parsedValue: {
immediate: true,
handler(val) {
if (this.picker) {
this.picker.value = val;
}
},
},
defaultValue(val) {
// NOTE: should eventually move to jsx style picker + panel ?
if (this.picker) {
this.picker.defaultValue = val;
}
},
value(val, oldVal) {
if (
!valueEquals(val, oldVal) &&
!this.pickerVisible &&
this.validateEvent
) {
this.dispatch("ElFormItem", "el.form.change", val);
}
},
},
computed: {
ranged() {
return this.type.indexOf("range") > -1;
},
reference() {
const reference = this.$refs.reference;
return reference.$el || reference;
},
refInput() {
if (this.reference) {
return [].slice.call(this.reference.querySelectorAll("input"));
}
return [];
},
valueIsEmpty() {
const val = this.value;
if (Array.isArray(val)) {
for (let i = 0, len = val.length; i < len; i++) {
if (val[i]) {
return false;
}
}
} else {
if (val) {
return false;
}
}
return true;
},
triggerClass() {
return (
this.prefixIcon ||
(this.type.indexOf("time") !== -1 ? "el-icon-time" : "el-icon-date")
);
},
selectionMode() {
if (this.type === "week") {
return "week";
} else if (this.type === "month") {
return "month";
} else if (this.type === "year") {
return "year";
} else if (this.type === "dates") {
return "dates";
} else if (this.type === "months") {
return "months";
} else if (this.type === "years") {
return "years";
}
return "day";
},
haveTrigger() {
if (typeof this.showTrigger !== "undefined") {
return this.showTrigger;
}
return HAVE_TRIGGER_TYPES.indexOf(this.type) !== -1;
},
displayValue() {
const formattedValue = formatAsFormatAndType(
this.parsedValue,
this.format,
this.type,
this.rangeSeparator
);
if (Array.isArray(this.userInput)) {
return [
this.userInput[0] || (formattedValue && formattedValue[0]) || "",
this.userInput[1] || (formattedValue && formattedValue[1]) || "",
];
} else if (this.userInput !== null) {
return this.userInput;
} else if (formattedValue) {
return this.type === "dates" ||
this.type === "years" ||
this.type === "months"
? formattedValue.join(", ")
: formattedValue;
} else {
return "";
}
},
parsedValue() {
if (!this.value) return this.value; // component value is not set
if (this.type === "time-select") return this.value; // time-select does not require parsing, this might change in next major version
const valueIsDateObject =
isDateObject(this.value) ||
(Array.isArray(this.value) && this.value.every(isDateObject));
if (valueIsDateObject) {
return this.value;
}
if (this.valueFormat) {
return (
parseAsFormatAndType(
this.value,
this.valueFormat,
this.type,
this.rangeSeparator
) || this.value
);
}
// NOTE: deal with common but incorrect usage, should remove in next major version
// user might provide string / timestamp without value-format, coerce them into date (or array of date)
return Array.isArray(this.value)
? this.value.map((val) => new Date(val))
: new Date(this.value);
},
_elFormItemSize() {
return (this.elFormItem || {}).elFormItemSize;
},
pickerSize() {
return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
},
pickerDisabled() {
return this.disabled || (this.elForm || {}).disabled;
},
firstInputId() {
const obj = {};
let id;
if (this.ranged) {
id = this.id && this.id[0];
} else {
id = this.id;
}
if (id) obj.id = id;
return obj;
},
secondInputId() {
const obj = {};
let id;
if (this.ranged) {
id = this.id && this.id[1];
}
if (id) obj.id = id;
return obj;
},
},
created() {
// vue-popper
this.popperOptions = {
boundariesPadding: 0,
gpuAcceleration: false,
};
this.placement = PLACEMENT_MAP[this.align] || PLACEMENT_MAP.left;
this.$on("fieldReset", this.handleFieldReset);
},
mounted() {
this.showPicker();
},
methods: {
focus() {
if (!this.ranged) {
this.$refs.reference.focus();
} else {
this.handleFocus();
}
},
blur() {
this.refInput.forEach((input) => input.blur());
},
// {parse, formatTo} Value deals maps component value with internal Date
parseValue(value) {
const isParsed =
isDateObject(value) ||
(Array.isArray(value) && value.every(isDateObject));
if (this.valueFormat && !isParsed) {
return (
parseAsFormatAndType(
value,
this.valueFormat,
this.type,
this.rangeSeparator
) || value
);
} else {
return value;
}
},
formatToValue(date) {
const isFormattable =
isDateObject(date) || (Array.isArray(date) && date.every(isDateObject));
if (this.valueFormat && isFormattable) {
return formatAsFormatAndType(
date,
this.valueFormat,
this.type,
this.rangeSeparator
);
} else {
return date;
}
},
// {parse, formatTo} String deals with user input
parseString(value) {
const type = Array.isArray(value)
? this.type
: this.type.replace("range", "");
return parseAsFormatAndType(value, this.format, type);
},
formatToString(value) {
const type = Array.isArray(value)
? this.type
: this.type.replace("range", "");
return formatAsFormatAndType(value, this.format, type);
},
handleMouseEnter() {
if (this.readonly || this.pickerDisabled) return;
if (!this.valueIsEmpty && this.clearable) {
this.showClose = true;
}
},
handleChange() {
if (this.userInput) {
const value = this.parseString(this.displayValue);
if (value) {
this.picker.value = value;
if (this.isValidValue(value)) {
this.emitInput(value);
this.userInput = null;
}
}
}
if (this.userInput === "") {
this.emitInput(null);
this.emitChange(null);
this.userInput = null;
}
},
handleStartInput(event) {
if (this.userInput) {
this.userInput = [event.target.value, this.userInput[1]];
} else {
this.userInput = [event.target.value, null];
}
},
handleEndInput(event) {
if (this.userInput) {
this.userInput = [this.userInput[0], event.target.value];
} else {
this.userInput = [null, event.target.value];
}
},
handleStartChange(event) {
const value = this.parseString(this.userInput && this.userInput[0]);
if (value) {
this.userInput = [this.formatToString(value), this.displayValue[1]];
const newValue = [value, this.picker.value && this.picker.value[1]];
this.picker.value = newValue;
if (this.isValidValue(newValue)) {
this.emitInput(newValue);
this.userInput = null;
}
}
},
handleEndChange(event) {
const value = this.parseString(this.userInput && this.userInput[1]);
if (value) {
this.userInput = [this.displayValue[0], this.formatToString(value)];
const newValue = [this.picker.value && this.picker.value[0], value];
this.picker.value = newValue;
if (this.isValidValue(newValue)) {
this.emitInput(newValue);
this.userInput = null;
}
}
},
handleClickIcon(event) {
if (this.readonly || this.pickerDisabled) return;
if (this.showClose) {
this.valueOnOpen = this.value;
event.stopPropagation();
this.emitInput(null);
this.emitChange(null);
this.showClose = false;
if (this.picker && typeof this.picker.handleClear === "function") {
this.picker.handleClear();
}
} else {
this.pickerVisible = !this.pickerVisible;
}
},
handleClose() {
if (!this.pickerVisible) return;
this.pickerVisible = false;
if (
this.type === "dates" ||
this.type === "years" ||
this.type === "months"
) {
// restore to former value
const oldValue =
parseAsFormatAndType(
this.valueOnOpen,
this.valueFormat,
this.type,
this.rangeSeparator
) || this.valueOnOpen;
this.emitInput(oldValue);
}
},
handleFieldReset(initialValue) {
this.userInput = initialValue === "" ? null : initialValue;
},
handleFocus() {
const type = this.type;
if (HAVE_TRIGGER_TYPES.indexOf(type) !== -1 && !this.pickerVisible) {
this.pickerVisible = true;
}
this.$emit("focus", this);
},
handleKeydown(event) {
const keyCode = event.keyCode;
// ESC
if (keyCode === 27) {
this.pickerVisible = false;
event.stopPropagation();
return;
}
// Tab
if (keyCode === 9) {
if (!this.ranged) {
this.handleChange();
this.pickerVisible = this.picker.visible = false;
this.blur();
event.stopPropagation();
} else {
// user may change focus between two input
setTimeout(() => {
if (this.refInput.indexOf(document.activeElement) === -1) {
this.pickerVisible = false;
this.blur();
event.stopPropagation();
}
}, 0);
}
return;
}
// Enter
if (keyCode === 13) {
if (
this.userInput === "" ||
this.isValidValue(this.parseString(this.displayValue))
) {
this.handleChange();
this.pickerVisible = this.picker.visible = false;
this.blur();
}
event.stopPropagation();
return;
}
// if user is typing, do not let picker handle key input
if (this.userInput) {
event.stopPropagation();
return;
}
// delegate other keys to panel
if (this.picker && this.picker.handleKeydown) {
this.picker.handleKeydown(event);
}
},
handleRangeClick() {
const type = this.type;
if (HAVE_TRIGGER_TYPES.indexOf(type) !== -1 && !this.pickerVisible) {
this.pickerVisible = true;
}
this.$emit("focus", this);
},
hidePicker() {
if (this.picker) {
this.picker.resetView && this.picker.resetView();
this.pickerVisible = this.picker.visible = false;
// this.destroyPopper();
}
},
showPicker() {
if (this.$isServer) return;
if (!this.picker) {
this.mountPicker();
}
this.pickerVisible = this.picker.visible = true;
// this.updatePopper();
this.picker.value = this.parsedValue;
this.picker.resetView && this.picker.resetView();
this.$nextTick(() => {
this.picker.adjustSpinners && this.picker.adjustSpinners();
});
},
mountPicker() {
this.picker = new Vue(this.panel).$mount();
this.picker.defaultValue = this.defaultValue;
this.picker.defaultTime = this.defaultTime;
this.picker.popperClass = this.popperClass;
this.popperElm = this.picker.$el;
this.picker.width = this.reference.getBoundingClientRect().width;
this.picker.showTime =
this.type === "datetime" || this.type === "datetimerange";
this.picker.selectionMode = this.selectionMode;
this.picker.unlinkPanels = this.unlinkPanels;
this.picker.arrowControl =
this.arrowControl || this.timeArrowControl || false;
this.$watch("format", (format) => {
this.picker.format = format;
});
const updateOptions = () => {
const options = this.pickerOptions;
if (options && options.selectableRange) {
let ranges = options.selectableRange;
const parser = TYPE_VALUE_RESOLVER_MAP.datetimerange.parser;
const format = DEFAULT_FORMATS.timerange;
ranges = Array.isArray(ranges) ? ranges : [ranges];
this.picker.selectableRange = ranges.map((range) =>
parser(range, format, this.rangeSeparator)
);
}
for (const option in options) {
if (
options.hasOwnProperty(option) &&
// 忽略 time-picker 的该配置项
option !== "selectableRange"
) {
this.picker[option] = options[option];
}
}
// main format must prevail over undocumented pickerOptions.format
if (this.format) {
this.picker.format = this.format;
}
};
updateOptions();
this.unwatchPickerOptions = this.$watch(
"pickerOptions",
() => updateOptions(),
{ deep: true }
);
this.$el.appendChild(this.picker.$el);
this.picker.resetView && this.picker.resetView();
this.picker.$on("dodestroy", this.doDestroy);
this.picker.$on("pick", (date = "", visible = false) => {
this.userInput = null;
// this.pickerVisible = this.picker.visible = visible;
this.emitInput(date);
this.picker.resetView && this.picker.resetView();
});
// this.picker.$on("select-range", (start, end, pos) => {
// if (this.refInput.length === 0) return;
// if (!pos || pos === "min") {
// this.refInput[0].setSelectionRange(start, end);
// this.refInput[0].focus();
// } else if (pos === "max") {
// this.refInput[1].setSelectionRange(start, end);
// this.refInput[1].focus();
// }
// });
},
unmountPicker() {
if (this.picker) {
this.picker.$destroy();
this.picker.$off();
if (typeof this.unwatchPickerOptions === "function") {
this.unwatchPickerOptions();
}
this.picker.$el.parentNode.removeChild(this.picker.$el);
}
},
emitChange(val) {
// determine user real change only
if (!valueEquals(val, this.valueOnOpen)) {
this.$emit("change", val);
this.valueOnOpen = val;
if (this.validateEvent) {
this.dispatch("ElFormItem", "el.form.change", val);
}
}
},
emitInput(val) {
const formatted = this.formatToValue(val);
if (!valueEquals(this.value, formatted)) {
this.$emit("input", formatted);
}
},
isValidValue(value) {
if (!this.picker) {
this.mountPicker();
}
if (this.picker.isValidValue) {
return value && this.picker.isValidValue(value);
} else {
return true;
}
},
},
};
</script>
import Picker from "../picker.vue";
import DatePanel from "../panel/date";
import DateRangePanel from "../panel/date-range";
import MonthRangePanel from "../panel/month-range";
const getPanel = function (type) {
if (type === "daterange" || type === "datetimerange") {
return DateRangePanel;
} else if (type === "monthrange") {
return MonthRangePanel;
}
return DatePanel;
};
export default {
mixins: [Picker],
name: "DatePicker",
props: {
type: {
type: String,
default: "date",
},
timeArrowControl: Boolean,
},
watch: {
type(type) {
if (this.picker) {
this.unmountPicker();
this.panel = getPanel(type);
this.mountPicker();
} else {
this.panel = getPanel(type);
}
},
},
created() {
this.panel = getPanel(this.type);
},
};
import Picker from '../picker';
import TimePanel from '../panel/time';
import TimeRangePanel from '../panel/time-range';
export default {
mixins: [Picker],
name: 'ElTimePicker',
props: {
isRange: Boolean,
arrowControl: Boolean
},
data() {
return {
type: ''
};
},
watch: {
isRange(isRange) {
if (this.picker) {
this.unmountPicker();
this.type = isRange ? 'timerange' : 'time';
this.panel = isRange ? TimeRangePanel : TimePanel;
this.mountPicker();
} else {
this.type = isRange ? 'timerange' : 'time';
this.panel = isRange ? TimeRangePanel : TimePanel;
}
}
},
created() {
this.type = this.isRange ? 'timerange' : 'time';
this.panel = this.isRange ? TimeRangePanel : TimePanel;
}
};
import Picker from '../picker';
import Panel from '../panel/time-select';
export default {
mixins: [Picker],
name: 'ElTimeSelect',
componentName: 'ElTimeSelect',
props: {
type: {
type: String,
default: 'time-select'
}
},
beforeCreate() {
this.panel = Panel;
}
};
......@@ -49,8 +49,8 @@ Vue.prototype.$clearSelection = clearSelection;
import { cloneDeep } from 'lodash-es';
Vue.prototype.$cloneDeep = cloneDeep;
import DatePicker from '@/pages/engineSearch/components/date-picker';
Vue.use(DatePicker);
// import DatePicker from '@/pages/engineSearch/components/date-picker';
// Vue.use(DatePicker);
Vue.config.productionTip = false;
new Vue({
......
......@@ -210,8 +210,11 @@
<script>
import { findBottomSubarrays } from '@/utils';
import DatePicker from './date-picker';
export default {
components: {},
components: {
DatePicker
},
props: {
dict: {
required: true,
......
......@@ -2,7 +2,6 @@
<div>
<el-dialog
:title="title"
:destroy-on-close="true"
:visible.sync="Visible"
width="650px"
@close="handleClose"
......@@ -140,6 +139,44 @@
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="资源类型" prop="resourceType">
<el-radio-group v-model="form.resourceType">
<el-radio :label="1">从资源列表获取</el-radio>
<el-radio :label="2">自定义资源地址</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24" v-show="form.resourceType == 1">
<el-form-item label="资源" prop="resourceUrl">
<select-res v-model="form.resourceUrl" :clearable="false"></select-res>
</el-form-item>
</el-col>
<el-col :span="24" v-show="form.resourceType == 2">
<el-form-item class="url-params-box" label="资源">
<el-form-item
class="url-params"
v-for="(v, i) in form.urls"
:key="i"
:prop="`urls.${i}.value`"
:rules="[{ required: false, validator: validatorUrl, trigger: 'blur' }]"
>
<el-input class="mr-[10px]" v-model="v.value" placeholder="请输入链接地址" />
<div class="flex">
<el-button type="primary" @click="changeParams(i, 'add')">
<i class="el-icon-plus"></i>
</el-button>
<el-button type="danger" v-if="i > 0" @click="changeParams(i, 'remove')"
><i class="el-icon-minus"></i
></el-button>
</div>
</el-form-item>
</el-form-item>
</el-col>
</el-row>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button size="medium" @click="handleRest">重 置</el-button>
......@@ -153,14 +190,15 @@
<script>
import { saveMenu } from '@/api/system';
import IconSelect from './IconSelect.vue';
import iconJson from '@/assets/icon.json';
import IconSelect from '@/components/IconSelect/IconSelect';
import SelectRes from './SelectRes.vue';
import Treeselect from '@riophae/vue-treeselect';
import '@riophae/vue-treeselect/dist/vue-treeselect.css';
export default {
components: {
IconSelect,
Treeselect
Treeselect,
SelectRes
},
props: {
title: {
......@@ -186,7 +224,6 @@ export default {
},
data() {
return {
iconJson,
loading: false,
defaultProps: {
children: 'childList',
......@@ -195,7 +232,7 @@ export default {
form: {
name: '', // 菜单名称
url: '', // 路由地址
ancestors: '', // 当前激活根目录
ancestors: '', // 当前节点父id路径,“,”分割
parentId: 0, // 父菜单ID,一级菜单的该字段值为-1
linkType: 0, // 链接方式 (0.普通,1.弹出,2.脚本)
imgPath: '', // 主菜单图标,主菜单图标的css样式名
......@@ -212,7 +249,14 @@ export default {
status: 1, // 菜单状态 (0.停用,1.启用)
cache: 0, // 是否缓存
activeDir: '', // 激活菜单
hideChildrenInMenu: 1 // 是否隐藏子菜单
resourceType: 1, // 资源类型 (1.按资源id,1.链接地址)
resourceUrl: [], // 资源id或者路径
hideChildrenInMenu: 1, // 是否隐藏子菜单
urls: [
{
value: ''
}
]
},
rules: {
name: [{ required: true, message: '请输入菜单名称', trigger: 'blur' }],
......@@ -238,7 +282,19 @@ export default {
this.$refs.form.validate(async (valid) => {
if (valid) {
this.loading = true;
let res = await saveMenu(this.form);
let resourceUrl;
let form = { ...this.form };
if (this.form.resourceType === 1) {
resourceUrl = this.form.resourceUrl.join(',');
} else {
resourceUrl = this.form.urls.map((v) => v.value).join(',');
}
this.$delete(form, 'resourceType');
this.$delete(form, 'urls');
let res = await saveMenu({
...form,
resourceUrl
});
let { code, msg } = res.data;
if (code === 1) {
this.$message.success(msg);
......@@ -258,8 +314,29 @@ export default {
onEdit(row) {
setTimeout(() => {
this.form = { ...row };
this.$set(this.form, 'resourceType', 1);
this.$set(this.form, 'urls', [{ value: '' }]);
this.form.resourceUrl = [];
if (row.resourceUrl) {
let arr = row.resourceUrl.split(',');
if (this.isUrl(row.resourceUrl)) {
this.form.urls = arr.map((v) => {
return {
value: v
};
});
this.form.resourceType = 2;
this.form.resourceUrl = arr;
} else {
this.form.resourceUrl = arr.map(Number);
}
}
}, 10);
},
// 判断资源是否是链接
isUrl(val) {
return val.startsWith('/');
},
// 重置
handleRest() {
this.$resetForm('form');
......@@ -280,19 +357,55 @@ export default {
children: node.childList
};
},
changeParent(row) {
console.log(row);
// 判断是否存在链接
isRepeat(val) {
return this.form.urls.filter((v) => v.value == val).length > 1;
},
// 校验链接地址
validatorUrl(rule, value, callback) {
if (!value) {
if (rule.required) {
callback(new Error('请输入链接'));
} else {
callback();
}
} else if (this.isRepeat(value)) {
callback(new Error('重复的链接地址'));
} else {
callback();
}
},
changeParams(index, type) {
if (type == 'add') {
let obj = {
value: ''
};
if (this.form.urls.some((v) => !v.value)) {
this.$message.warning('请先完成前面地址的填写');
return;
}
this.form.urls.splice(index + 1, 0, obj);
} else {
this.form.urls.splice(index, 1);
}
// this.$forceUpdate();
}
}
};
</script>
<style lang="less" scoped>
// :deep(.el-dialog__body) {
// max-height: 600px;
// overflow: auto;
// }
:deep(.el-dialog__body) {
max-height: 650px;
overflow: auto;
}
:deep(.el-select) {
width: 100%;
}
:deep(.url-params) {
margin-bottom: 22px;
.el-form-item__content {
display: flex;
}
}
</style>
<template>
<div class="w-full">
<el-cascader
clearable
filterable
:value="value"
placeholder="请选择资源"
:options="resourceList"
:props="{
value: 'id',
label: 'name',
multiple: true,
emitPath: false,
expandTrigger: 'hover'
}"
v-bind="$attrs"
v-on="$listeners"
@change="handleChange"
></el-cascader>
</div>
</template>
<script>
import { getResourceGroup } from '@/api/system';
export default {
model: {
prop: 'value',
event: 'change'
},
props: {
value: {
default: ''
},
dict: {
type: Object,
default: () => {}
},
form: {
type: Object,
default: () => {}
}
},
data() {
return {
resourceList: [] // 资源列表
};
},
created() {
this.getResourceGroup();
},
methods: {
// 获取资源列表
async getResourceGroup() {
this.loading = true;
let res = await getResourceGroup({
page: 1,
size: -1
});
if (res.data.code == 1) {
let { data } = res.data.data;
this.resourceList = this.formatGroup(data);
}
},
// 格式化分组
formatGroup(data) {
let arr = [];
Object.keys(data).forEach((key, index) => {
let group = {};
group = {
name: key,
id: -index,
children: []
};
group.children.push(...data[key]);
arr.push(group);
});
return arr;
},
handleChange(value) {
this.$emit('change', value);
}
}
};
</script>
<style lang="less" scoped>
:deep(.el-cascader) {
width: 100%;
}
</style>
......@@ -193,6 +193,7 @@ export default {
width: 100%;
}
:deep(.url-params) {
margin-bottom: 22px;
.el-form-item__content {
display: flex;
}
......
......@@ -29,7 +29,7 @@ function resSuccess(response) {
if (response.data.code !== undefined && response.data.msg !== undefined) {
// 取出数据
let { code, msg } = response.data;
if (code == -1) {
if (code == -1 || code == 405) {
message.error({
message: msg
});
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment