Commit a37c3742 authored by “yiyousong”'s avatar “yiyousong”

feat: 评价汇总和排队汇总接口调试

parent c1d0677e
......@@ -17,3 +17,21 @@ export const getEvaSystemList = (data) => {
data,
});
};
// 获取排队汇总统计列表
export const getQueueStatList = (data) => {
return request({
url: `/bill/ph/queue/stat/list`,
method: "post",
data,
});
};
// 获取评价汇总统计列表
export const getEvaStatList = (data) => {
return request({
url: `/bill/pj/evaluate/stat/list`,
method: "post",
data,
});
};
......@@ -49,6 +49,9 @@ Vue.prototype.$clearSelection = clearSelection;
import { cloneDeep } from "lodash-es";
Vue.prototype.$cloneDeep = cloneDeep;
import DatePicker from "@/pages/engineSearch/components/date-picker";
Vue.use(DatePicker);
Vue.config.productionTip = false;
new Vue({
router,
......
<template>
<div class="flex h-full w-full flex-col">
<Search></Search>
<Search @search="handleSearch"></Search>
<div
class="main flex w-full flex-1 items-center justify-center rounded-[4px] bg-white p-[20px]"
>
<div class="h-full w-full" v-if="tableData.length">
<div class="h-full w-full">
<TableHeader>
<el-button size="small" slot="left" type="primary">导出</el-button>
<el-button
size="small"
slot="left"
type="primary"
:loading="exportLoading"
@click="exportExcel"
>导出</el-button
>
</TableHeader>
<y-table
ref="MyTable"
:loading="loading"
:data="tableData"
:column="column"
border
tooltip-effect="dark"
:max-height="600"
:row-key="(row) => row.id"
@selection-change="handleSelectionChange"
></y-table>
<div>
<y-table
ref="MyTable"
:loading="loading"
:data="tableData"
:column="column"
border
tooltip-effect="dark"
:max-height="480"
:row-key="(row) => row.id"
@selection-change="handleSelectionChange"
></y-table>
</div>
<Pagination
:total="total"
:current.sync="current"
:size.sync="size"
@get="getData"
@get="getEvaStatList"
></Pagination>
</div>
<el-empty
<!-- <el-empty
v-else
description="请检索数据"
:image="require('@/assets/img/empty.png')"
></el-empty>
></el-empty> -->
</div>
</div>
</template>
......@@ -38,6 +47,10 @@
<script>
import Search from "./components/Search.vue";
import TableHeader from "@/components/TableHeader.vue";
import { getEvaStatList } from "@/api/engine";
import { formatSeconds, dataSection } from "@/utils";
import { export2Excel } from "@/utils/exportExcel";
import storage from "@/utils/storage";
export default {
components: {
Search,
......@@ -46,19 +59,201 @@ export default {
data() {
return {
loading: false,
exportLoading: false,
siteId: storage.get(2, "siteId"),
page: 1,
size: 10,
total: 0,
current: 1,
tableData: [],
column: [],
column: [
{
label: "全选",
type: "selection",
width: "55",
align: "center",
reserveSelection: true,
},
{
label: "序号",
type: "index",
width: "55",
align: "center",
index: (index) => {
return (this.current - 1) * this.size + index + 1;
},
},
{
label: "站点名称",
prop: "siteName",
align: "center",
},
{
label: "部门名称",
prop: "sectionName",
align: "center",
},
{
label: "评价选型",
prop: "pjOption",
align: "center",
},
{
label: "窗口编号",
prop: "windowFromnum",
align: "center",
},
{
label: "年份",
prop: "year",
align: "center",
},
{
label: "月份",
prop: "month",
align: "center",
},
{
label: "日期",
prop: "day",
align: "center",
},
{
label: "评价数量",
prop: "pjCount",
align: "center",
},
],
selectionRows: [],
searchForm: {
groupList: [],
// sectionNameNotList: [""],
// businessNotList: [""],
// windowFromnumNotList: [""],
pjOptionList: [],
sectionNameList: [],
windowFromnumList: [],
year: "",
month: "",
day: "",
},
};
},
created() {
this.getEvaStatList();
},
methods: {
getData() {},
async evaStatList(form = {}) {
let res = await getEvaStatList({
page: this.current,
size: this.size,
siteId: this.siteId,
...this.searchForm,
...form,
});
if (res.data.code == 1) {
let { data, total } = res.data.data;
return {
data,
total,
};
} else {
return {
data: [],
total: 0,
};
}
},
async getEvaStatList() {
this.loading = true;
let { data, total } = await this.evaStatList();
data.forEach((v) => {
v.waitTime = v.waitTime ? formatSeconds(v.waitTime) : "";
});
this.total = total;
this.tableData = data;
this.loading = false;
},
handleSelectionChange(rows) {
this.selectionRows = rows;
},
handleSearch(form) {
let { type, pjOption, deptName, windowName, year, month, day } = form;
if (type == "year") {
this.searchForm.groupList = ["year"];
this.searchForm.year = year;
} else if (type == "month") {
this.searchForm.groupList = ["year", "month"];
if (month) {
let monthNum = month.split("-");
this.searchForm.year = monthNum[0];
this.searchForm.month = monthNum[1];
}
} else {
this.searchForm.groupList = ["year", "month", "day"];
if (day) {
let dayNum = day.split("-");
this.searchForm.year = dayNum[0];
this.searchForm.month = dayNum[1];
this.searchForm.day = dayNum[2];
}
}
if (pjOption) {
this.searchForm.pjOptionList = [pjOption];
} else {
this.searchForm.pjOptionList = [];
}
if (deptName) {
this.searchForm.sectionNameList = [deptName];
} else {
this.searchForm.sectionNameList = [];
}
if (windowName) {
this.searchForm.windowFromnumList = [windowName];
} else {
this.searchForm.windowFromnumList = [];
}
this.current = 1;
this.getEvaStatList();
},
// 导出表格
exportExcel() {
this.exportLoading = true;
let table = this.column.filter((v) => v.prop);
let tHeader = table.map((v) => v.label);
let filterVal = table.map((v) => v.prop);
if (this.selectionRows.length) {
let data = this.$cloneDeep(this.selectionRows);
export2Excel(
tHeader,
filterVal,
data,
"排队数据汇总报表" + this.$moment().format("YYYYMMDDHHmmss")
);
} else {
dataSection(this.queueStatList, {}, (data) => {
if (!data.length) {
this.$message.warning("暂无数据");
return;
}
data.forEach((v) => {
v.waitTime = v.waitTime ? formatSeconds(v.waitTime) : "";
});
export2Excel(
tHeader,
filterVal,
data,
"排队数据汇总报表" + this.$moment().format("YYYYMMDDHHmmss")
);
});
}
this.exportLoading = false;
},
},
};
</script>
......
<template>
<div class="flex h-full w-full flex-col">
<Search></Search>
<Search @search="handleSearch"></Search>
<div
class="main flex w-full flex-1 items-center justify-center rounded-[4px] bg-white p-[20px]"
>
<div class="h-full w-full" v-if="tableData.length">
<div class="h-full w-full">
<TableHeader>
<el-button size="small" slot="left" type="primary">导出</el-button>
<el-button
size="small"
slot="left"
type="primary"
:loading="exportLoading"
@click="exportExcel"
>导出</el-button
>
</TableHeader>
<y-table
ref="MyTable"
:loading="loading"
:data="tableData"
:column="column"
border
tooltip-effect="dark"
:max-height="600"
:row-key="(row) => row.id"
@selection-change="handleSelectionChange"
></y-table>
<div>
<y-table
ref="MyTable"
:loading="loading"
:data="tableData"
:column="column"
border
tooltip-effect="dark"
:max-height="480"
:row-key="(row) => row.id"
@selection-change="handleSelectionChange"
></y-table>
</div>
<Pagination
:total="total"
:current.sync="current"
:size.sync="size"
@get="getData"
@get="getQueueStatList"
></Pagination>
</div>
<el-empty
<!-- <el-empty
v-else
description="请检索数据"
:image="require('@/assets/img/empty.png')"
></el-empty>
></el-empty> -->
</div>
</div>
</template>
......@@ -38,6 +47,10 @@
<script>
import Search from "./components/Search.vue";
import TableHeader from "@/components/TableHeader.vue";
import { getQueueStatList } from "@/api/engine";
import { formatSeconds, dataSection } from "@/utils";
import { export2Excel } from "@/utils/exportExcel";
import storage from "@/utils/storage";
export default {
components: {
Search,
......@@ -46,19 +59,205 @@ export default {
data() {
return {
loading: false,
exportLoading: false,
siteId: storage.get(2, "siteId"),
page: 1,
size: 10,
total: 0,
current: 1,
tableData: [],
column: [],
column: [
{
label: "全选",
type: "selection",
width: "55",
align: "center",
reserveSelection: true,
},
{
label: "序号",
type: "index",
width: "55",
align: "center",
index: (index) => {
return (this.current - 1) * this.size + index + 1;
},
},
{
label: "站点名称",
prop: "siteName",
align: "center",
},
{
label: "业务名称",
prop: "business",
align: "center",
},
{
label: "部门名称",
prop: "sectionName",
align: "center",
},
{
label: "窗口编号",
prop: "windowFromnum",
align: "center",
},
{
label: "年份",
prop: "year",
align: "center",
},
{
label: "月份",
prop: "month",
align: "center",
},
{
label: "日期",
prop: "day",
align: "center",
},
{
label: "办理量",
prop: "phCount",
align: "center",
},
{
label: "平均等待时长",
prop: "waitTime",
align: "center",
},
],
selectionRows: [],
searchForm: {
groupList: [],
// sectionNameNotList: [""],
// businessNotList: [""],
// windowFromnumNotList: [""],
businessList: [],
sectionNameList: [],
windowFromnumList: [],
year: "",
month: "",
day: "",
},
};
},
created() {
this.getQueueStatList();
},
methods: {
getData() {},
async queueStatList(form = {}) {
let res = await getQueueStatList({
page: this.current,
size: this.size,
siteId: this.siteId,
...this.searchForm,
...form,
});
if (res.data.code == 1) {
let { data, total } = res.data.data;
return {
data,
total,
};
} else {
return {
data: [],
total: 0,
};
}
},
async getQueueStatList() {
this.loading = true;
let { data, total } = await this.queueStatList();
data.forEach((v) => {
v.waitTime = v.waitTime ? formatSeconds(v.waitTime) : "";
});
this.total = total;
this.tableData = data;
this.loading = false;
},
handleSelectionChange(rows) {
this.selectionRows = rows;
},
handleSearch(form) {
let { type, businessName, deptName, windowName, year, month, day } = form;
if (type == "year") {
this.searchForm.groupList = ["year"];
this.searchForm.year = year;
} else if (type == "month") {
this.searchForm.groupList = ["year", "month"];
if (month) {
let monthNum = month.split("-");
this.searchForm.year = monthNum[0];
this.searchForm.month = monthNum[1];
}
} else {
this.searchForm.groupList = ["year", "month", "day"];
if (day) {
let dayNum = day.split("-");
this.searchForm.year = dayNum[0];
this.searchForm.month = dayNum[1];
this.searchForm.day = dayNum[2];
}
}
if (businessName) {
this.searchForm.businessList = [businessName];
} else {
this.searchForm.businessList = [];
}
if (deptName) {
this.searchForm.sectionNameList = [deptName];
} else {
this.searchForm.sectionNameList = [];
}
if (windowName) {
this.searchForm.windowFromnumList = [windowName];
} else {
this.searchForm.windowFromnumList = [];
}
this.current = 1;
this.getQueueStatList();
},
// 导出表格
exportExcel() {
this.exportLoading = true;
let table = this.column.filter((v) => v.prop);
let tHeader = table.map((v) => v.label);
let filterVal = table.map((v) => v.prop);
if (this.selectionRows.length) {
let data = this.$cloneDeep(this.selectionRows);
export2Excel(
tHeader,
filterVal,
data,
"排队数据汇总报表" + this.$moment().format("YYYYMMDDHHmmss")
);
} else {
dataSection(this.queueStatList, {}, (data) => {
if (!data.length) {
this.$message.warning("暂无数据");
return;
}
data.forEach((v) => {
v.waitTime = v.waitTime ? formatSeconds(v.waitTime) : "";
});
export2Excel(
tHeader,
filterVal,
data,
"排队数据汇总报表" + this.$moment().format("YYYYMMDDHHmmss")
);
});
}
this.exportLoading = false;
},
},
};
</script>
......
<template>
<div
class="search mb-[15px] flex h-[170px] w-full flex-col items-center justify-around"
>
<div class="flex gap-10">
<router-link v-for="(v, i) in subMenus" :key="i" :to="v.path">
{{ v.meta.title }}
</router-link>
</div>
<div class="flex items-center justify-center gap-2">
<div class="search-box">
<div class="flex gap-2">
<div class="item border">
<span class="pl-[15px] text-[14px]">选择时间</span>
<el-popover trigger="click" placement="bottom-start">
<div>
<div class="tab-box">
<div
:class="['tab-item', { active: active == 'year' }]"
@click="changeTab('year')"
>
按年
</div>
<div
:class="['tab-item', { active: active == 'month' }]"
@click="changeTab('month')"
>
按月
</div>
<div
:class="['tab-item', { active: active == 'date' }]"
@click="changeTab('date')"
>
按日
</div>
</div>
<div class="tab-content w-ull">
<date :type="active" @pick="handlePick"></date>
<!-- <y-date-picker
v-model="value1"
type="date"
placeholder="选择日期"
>
</y-date-picker> -->
</div>
</div>
<el-input
size="small"
placeholder="暂未选择"
slot="reference"
></el-input>
</el-popover>
</div>
<div class="item border">
<span class="pl-[15px] text-[14px]">选择业务</span>
<el-popover trigger="click" placement="bottom-start">
<el-input
size="small"
placeholder="暂未选择"
slot="reference"
></el-input>
</el-popover>
</div>
<div class="item border">
<span class="pl-[15px] text-[14px]">选择部门</span>
<el-popover trigger="click" placement="bottom-start">
<el-input
size="small"
placeholder="暂未选择"
slot="reference"
></el-input>
</el-popover>
</div>
<div class="item">
<span class="pl-[15px] text-[14px]">选择窗口</span>
<el-popover trigger="click" placement="bottom-start">
<el-input
size="small"
placeholder="暂未选择"
slot="reference"
></el-input>
</el-popover>
</div>
</div>
<div class="btn">检索</div>
</div>
<div class="back-btn" @click="$router.push('/engine')">返回</div>
</div>
</div>
</template>
<script>
import date from "./date-picker/src/panel/date";
import { findBottomSubarrays } from "@/utils";
export default {
components: {
date,
},
data() {
return {
subMenus: [],
active: "date",
};
},
computed: {
activeKey() {
return this.$route.path;
},
},
created() {
this.getSubMenus();
},
mounted() {},
methods: {
changeRouter(e) {
this.$router.push(e.name);
},
// 获取当前顶层路由下的所有子路由
getSubMenus() {
let path = this.$route?.meta.parentPath
? this.$route.meta.parentPath
: this.$route.path;
let options = this.$router.options.routes[0].children;
let curRouters = options.filter((v) => v.path == path);
this.subMenus = findBottomSubarrays(curRouters).filter(
(v) => !v.meta.hidden
);
},
changeTab(key) {
this.active = key;
},
handlePick(val) {
console.log(val);
},
},
};
</script>
<style lang="less" scoped>
.search {
background: url("@/assets/img/engineSearch_bg.svg") no-repeat center / 100%
100%;
a {
color: rgba(254, 254, 254, 0.65);
}
.router-link-active {
color: #fff;
font-weight: 600;
}
}
.border {
border-right: 1px solid #ccc;
}
.search-box {
height: 68px;
width: 60%;
padding: 0px 15px;
background: #fff;
border-radius: 34px;
display: flex;
align-items: center;
}
.btn,
.back-btn {
width: 96px;
height: 60px;
flex-shrink: 0;
font-weight: 700;
color: #fff;
line-height: 60px;
text-align: center;
background: linear-gradient(90deg, #5ab6ff, #2e9aff);
border-radius: 30px;
cursor: pointer;
}
.back-btn {
color: var(--primary);
background: #fff;
}
:deep(.el-input) {
.el-input__inner {
border: none;
}
}
.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;
}
</style>
......@@ -7,19 +7,147 @@
{{ v.meta.title }}
</router-link>
</div>
<div class="search-box"></div>
<div class="flex items-center justify-center gap-2">
<el-form ref="form" size="small" inline :model="form">
<el-form-item prop="type">
<el-select
style="width: 100px"
v-model="form.type"
placeholder="请选择"
>
<el-option label="按年" value="year"></el-option>
<el-option label="按月" value="month"></el-option>
<el-option label="按日" value="day"></el-option>
</el-select>
</el-form-item>
<el-form-item prop="year" v-if="form.type == 'year'">
<el-date-picker
style="width: 150px"
v-model="form.year"
type="year"
value-format="yyyy"
placeholder="选择年"
>
</el-date-picker>
</el-form-item>
<el-form-item prop="month" v-if="form.type == 'month'">
<el-date-picker
style="width: 150px"
v-model="form.month"
type="month"
value-format="yyyy-MM"
placeholder="选择月"
>
</el-date-picker>
</el-form-item>
<el-form-item prop="day" v-if="form.type == 'day'">
<el-date-picker
style="width: 150px"
v-model="form.day"
type="date"
value-format="yyyy-MM-dd"
placeholder="选择日"
>
</el-date-picker>
</el-form-item>
<el-form-item
prop="businessName"
v-if="$route.path == '/enginesearch/queueupdata'"
>
<el-select
style="width: 150px"
v-model="form.businessName"
placeholder="请选择业务"
clearable
>
<el-option
v-for="v in businessList"
:key="v.id"
:label="v.businessName"
:value="v.businessName"
></el-option>
</el-select>
</el-form-item>
<el-form-item prop="pjOption" v-else>
<el-select
style="width: 150px"
v-model="form.pjOption"
placeholder="请选择评价选项"
clearable
>
<el-option
v-for="(v, i) in pjOption"
:key="i"
:label="v"
:value="v"
></el-option>
</el-select>
</el-form-item>
<el-form-item prop="deptName">
<el-select
style="width: 150px"
v-model="form.deptName"
placeholder="请选择部门"
clearable
>
<el-option
v-for="v in deptList"
:key="v.id"
:label="v.name"
:value="v.name"
></el-option>
</el-select>
</el-form-item>
<el-form-item prop="windowName">
<el-select
style="width: 150px"
v-model="form.windowName"
placeholder="请选择窗口"
clearable
>
<el-option
v-for="v in windowList"
:key="v.id"
:label="`${v.name}-${v.fromnum}`"
:value="v.fromnum"
></el-option>
</el-select>
</el-form-item>
<el-form-item>
<el-button @click="handleSearch">检索</el-button>
</el-form-item>
<el-form-item>
<el-button @click="$router.push('/engine')">返回</el-button>
</el-form-item>
</el-form>
</div>
</div>
</template>
<script>
import { mapState } from "vuex";
import { findBottomSubarrays } from "@/utils";
let pjOption = ["非常满意", "满意", "基本满意", "不满意", "非常不满意"];
export default {
components: {},
data() {
return {
pjOption,
subMenus: [],
form: {
type: "year",
businessName: "",
deptName: "",
windowName: "",
year: "",
month: "",
day: "",
pjOption: "",
},
};
},
computed: {
...mapState("user", ["businessList", "deptList", "windowList"]),
activeKey() {
return this.$route.path;
},
......@@ -27,6 +155,7 @@ export default {
created() {
this.getSubMenus();
},
mounted() {},
methods: {
changeRouter(e) {
this.$router.push(e.name);
......@@ -42,12 +171,16 @@ export default {
(v) => !v.meta.hidden
);
},
handleSearch() {
this.$emit("search", this.form);
},
},
};
</script>
<style lang="less" scoped>
.search {
flex-shrink: 0;
background: url("@/assets/img/engineSearch_bg.svg") no-repeat center / 100%
100%;
a {
......@@ -58,10 +191,4 @@ export default {
font-weight: 600;
}
}
.search-box {
height: 68px;
width: 60%;
background: #fff;
border-radius: 34px;
}
</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
@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>
<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,
},
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>
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: "YDatePicker",
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 Panel from '../panel/time-select';
export default {
mixins: [Picker],
name: 'ElTimeSelect',
componentName: 'ElTimeSelect',
props: {
type: {
type: String,
default: 'time-select'
}
},
beforeCreate() {
this.panel = Panel;
}
};
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