个人组件库文档地址
DatePicker 日期选择器
基础用法
<vp-date-picker v-model="datePicker1" placeholder="请选择日期" />
<script>
export default {
data() {
return {
datePicker1: "",
};
},
};
</script>
不可选中
<vp-date-picker
v-model="datePicker2"
placeholder="请选择日期"
:picker-option="pickerOption"
@blur="handleDatePickerBlur"
@focus="handleDatePickerFocus"
@change="handleDatePickerChange"
/>
<script>
export default {
data() {
return {
datePicker2: "",
pickerOption: {
disabledDate(time) {
return time.getTime() > Date.now();
},
},
};
},
methods: {
handleDatePickerBlur(vm) {
console.log(vm);
},
handleDatePickerFocus(vm) {
console.log(vm);
},
handleDatePickerChange(vm) {
console.log(vm);
},
}
};
</script>
DatePicker 组件代码
<template>
<div class="vp-date-picker">
<vp-input
v-model="date"
:placeholder="placeholder"
@focus="handleInputFocus"
@blur="handleInputBlur($event)"
>
<template v-slot:prefix>
<span class="iconfont icon-rili"></span>
</template>
</vp-input>
<transition name="slide-fade">
<div :class="['vp-date-picker-container']" v-show="active">
<div class="vp-date-picker_control">
<div class="date_left">
<div class="date_pre_year" @click="handlePreYear">
<span class="iconfont icon-jiantou_yemian_xiangzuo_o"></span>
</div>
<div class="date_pre_month" @click="handlePreMonth">
<span class="iconfont icon-jiantou_liebiaoxiangzuo_o"></span>
</div>
</div>
<div class="date_text">
{{ currentTime.currentYear }} 年 {{ currentTime.currentMonth }} 月
</div>
<div class="date_right">
<div class="date_suffix_month" @click="handleSuffixMonth">
<span class="iconfont icon-jiantou_liebiaoxiangyou_o"></span>
</div>
<div class="date_suffix_year" @click="handleSuffixYear">
<span class="iconfont icon-jiantou_yemian_xiangyou_o"></span>
</div>
</div>
</div>
<table class="vp-date-table">
<thead>
<tr>
<th v-for="week in weekList" :key="week">
{{ week }}
</th>
</tr>
</thead>
<tbody>
<template v-for="row in rowLength">
<tr :key="row">
<td
v-for="td in 7"
:key="td"
:class="[
'activable',
(row - 1) * 7 + td - 1 <= preLastIndex ? 'preMonth_td' : '',
(row - 1) * 7 + td - 1 >= suffixFirstIndex
? 'suffixMonth_td'
: '',
currentTime.currentYear === nowYear &&
currentTime.currentMonth === nowMonth &&
dayList[(row - 1) * 7 + td - 1].day === nowDay &&
(row - 1) * 7 + td - 1 === currentDateIndex
? 'currentTime__tb'
: '',
]"
@click="
handleSelectDate(
dayList[(row - 1) * 7 + td - 1].day,
(row - 1) * 7 + td - 1,
$event
)
"
>
<div
:class="[
pickerOption &&
pickerOption.disabledDate(
dayList[(row - 1) * 7 + td - 1].date
)
? 'disabled_day'
: '',
]"
>
<span
:class="[
'date_table_span',
currentTime.currentYear === selectYear &&
currentTime.currentMonth === selectMonth &&
dayList[(row - 1) * 7 + td - 1].day === selectDay &&
(row - 1) * 7 + td - 1 > preLastIndex &&
(row - 1) * 7 + td - 1 < suffixFirstIndex
? 'select_time_tab'
: '',
]"
>{{ dayList[(row - 1) * 7 + td - 1].day }}</span
>
</div>
</td>
</tr>
</template>
</tbody>
</table>
</div>
</transition>
<transition name="slide-fade">
<div class="vp-option_san" v-show="active"></div>
</transition>
</div>
</template>
<script>
export default {
name: "vpDatePicker",
props: {
placeholder: {
type: String,
default: "",
},
value: {
type: Date | String,
},
pickerOption: {
type: Object,
default: () => {},
},
align: {
type: String,
default: "left",
},
},
watch: {
value: {
handler(newVal) {
let date = new Date(newVal);
let month = date.getMonth();
let year = date.getFullYear();
let day = date.getDate();
let monthList = [1, 3, 5, 7, 8, 10, 12];
if (month === 0 && day === 31) {
month = 1;
} else if (monthList.includes(month) && day === 31) {
month += 1;
} else if (!monthList.includes(month) && month !== 0 && day === 31) {
month += 1;
}
this.date = `${year}-${month < 10 ? "0" + month : month}-${
day < 10 ? "0" + day : day
}`;
},
},
currentTime: {
handler(newVal) {
this.getFullMonthDateList();
for (let i = this.preLastIndex + 1; i < this.suffixFirstIndex; i++) {
if (this.dayList[i] === this.selectDay) {
this.selectDayIndex = i;
break;
}
}
},
deep: true,
},
pickerOption: {
handler(newVal) {
if (!newVal) return;
console.log(newVal);
},
immediate: true,
deep: true,
},
},
data() {
return {
date: "",
active: false,
dayList: [],
weekList: ["日", "一", "二", "三", "四", "五", "六"],
currentTime: {
currentYear: "",
currentMonth: "",
currentDate: "",
},
currentDateIndex: "",
rowLength: 6,
preLastIndex: 0,
suffixFirstIndex: 0,
nowYear: new Date().getFullYear(),
nowMonth: new Date().getMonth() + 1,
nowDay: new Date().getDate(),
selectYear: "",
selectMonth: "",
selectDay: "",
selectDayIndex: "",
isClickContainer: false,
option: {
disabledDateFun: null,
},
};
},
created() {
this.initPickerOption();
this.initNowDate();
this.getFullMonthDateList();
},
mounted() {
this.handleMouseClick();
},
methods: {
initPickerOption() {
},
initNowDate() {
let date = new Date();
this.currentTime.currentYear = date.getFullYear();
this.currentTime.currentMonth = date.getMonth() + 1;
this.currentTime.currentDate = date.getDate();
},
getDateDay(year, month, day = 1) {
let date = new Date(year, month - 1, day);
return date.getDay();
},
getFullMonthDateList() {
let dayList = [];
let firstDay = this.getDateDay(
this.currentTime.currentYear,
this.currentTime.currentMonth,
1
);
let lastDay = new Date(
this.currentTime.currentYear,
this.currentTime.currentMonth,
0
).getDate();
for (let i = 1; i <= lastDay; i++) {
dayList.push({
day: i,
date: new Date(
this.currentTime.currentYear,
this.currentTime.currentMonth - 1,
i
),
});
}
let preDay = firstDay !== 0 ? firstDay : 0;
let preMonthLastDate = new Date(
this.currentTime.currentYear,
this.currentTime.currentMonth - 1,
0
).getDate();
this.preLastIndex = preDay - 1;
for (let i = 0; i < preDay; i++) {
dayList.unshift({
day: preMonthLastDate - i,
date: new Date(
this.currentTime.currentMonth === 1
? this.currentTime.currentYear - 1
: this.currentTime.currentYear,
this.currentTime.currentMonth === 1
? 11
: this.currentTime.currentMonth - 1 - 1,
preMonthLastDate - i
),
});
}
let suffixLength = 42 - dayList.length;
this.suffixFirstIndex = 42 - (42 - dayList.length);
for (let i = 1; i <= suffixLength; i++) {
dayList.push({
day: i,
date: new Date(
this.currentTime.currentMonth === 12
? this.currentTime.currentYear + 1
: this.currentTime.currentYear,
this.currentTime.currentMonth === 12
? 0
: this.currentTime.currentMonth + 1 - 1,
i
),
});
}
this.currentDateIndex = dayList.findIndex(
(item, index) =>
index > this.preLastIndex &&
index < this.suffixFirstIndex &&
item.day === new Date().getDate()
);
this.dayList = dayList;
},
handleInputBlur(e, event) {
this.$emit("blur", this);
},
handleInputFocus() {
this.active = true;
this.$emit("focus", this);
},
handleSelectDate(day, index, event) {
if (
event.target.parentElement.className === "disabled_day" ||
event.target?.lastElementChild?.className === "disabled_day"
) {
return;
}
if (this.preLastIndex < index && index < this.suffixFirstIndex) {
this.selectDayIndex = index;
this.selectDay = day;
this.selectYear = this.currentTime.currentYear;
this.selectMonth = this.currentTime.currentMonth;
} else if (index <= this.preLastIndex) {
let currentMonth = this.currentTime.currentMonth;
this.selectYear =
currentMonth === 1
? this.currentTime.currentYear - 1
: this.currentTime.currentYear;
this.selectMonth =
currentMonth - 1 < 1 ? 12 : this.currentTime.currentMonth - 1;
this.selectDay = day;
this.currentTime.currentMonth = this.selectMonth;
this.currentTime.currentDate = day;
this.currentTime.currentYear = this.selectYear;
} else if (index >= this.suffixFirstIndex) {
this.selectYear =
this.currentTime.currentMonth === 12
? this.currentTime.currentYear + 1
: this.currentTime.currentYear;
this.selectMonth =
this.currentTime.currentMonth === 12
? 1
: this.currentTime.currentMonth + 1;
this.currentTime.currentMonth =
this.currentTime.currentMonth === 12
? 1
: this.currentTime.currentMonth + 1;
this.selectDay = day;
this.currentTime.currentDate = day;
this.currentTime.currentYear = this.selectYear;
}
let monthList = [1, 3, 5, 7, 8, 10, 12];
let selectMonth = this.selectMonth;
if (monthList.includes(this.selectMonth) && this.selectDay === 31) {
selectMonth -= 1;
}
this.$emit(
"input",
new Date(this.selectYear, selectMonth, this.selectDay)
);
this.$emit(
"change",
new Date(this.selectYear, selectMonth, this.selectDay)
);
this.active = false;
},
handlePreYear() {
this.currentTime.currentYear = this.currentTime.currentYear - 1;
},
handleSuffixYear() {
this.currentTime.currentYear = this.currentTime.currentYear + 1;
},
handlePreMonth() {
let currentMonth = this.currentTime.currentMonth;
if (currentMonth === 1) {
this.currentTime.currentMonth = 12;
this.currentTime.currentYear = this.currentTime.currentYear - 1;
} else {
this.currentTime.currentMonth = currentMonth - 1;
}
},
handleSuffixMonth() {
this.currentTime.currentYear =
this.currentTime.currentMonth + 1 > 12
? this.currentTime.currentYear + 1
: this.currentTime.currentYear;
this.currentTime.currentMonth =
this.currentTime.currentMonth + 1 > 12
? 1
: this.currentTime.currentMonth + 1;
},
handleMouseClick() {
document.addEventListener(
"mouseup",
(e) => {
let flag = this.findParent(
e.target,
this.$el.querySelector(".vp-date-picker-container")
);
if (flag) {
this.active = true;
this.isClickContainer = true;
} else if (e.target !== this.$el.querySelector(".vp-input-inner")) {
this.active = false;
this.isClickContainer = false;
} else if (e.target === this.$el.querySelector(".vp-input-inner")) {
this.active = true;
this.isClickContainer = false;
}
},
true
);
},
findParent(target, parent) {
if (!target) {
return false;
}
if (target === parent || target?.parentElement === parent) {
return true;
}
return this.findParent(target.parentElement, parent);
},
},
};
</script>
<style lang="less" scoped>
.vp-date-picker {
position: relative;
display: inline-block;
.vp-input {
width: 220px;
.vp-input_inner {
&:hover {
cursor: pointer;
}
}
}
.slide-fade-enter-active {
transition: all 0.3s ease;
}
.slide-fade-leave-active {
transition: all 0.3s cubic-bezier(1, 0.5, 0.8, 1);
}
.slide-fade-enter,
.slide-fade-leave-to {
opacity: 0;
}
.vp-date-picker-container {
position: absolute;
display: inline-block;
box-sizing: border-box;
// width: 100%;
top: 135%;
z-index: 9999;
border: 1px solid #e4e7ed;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
background-color: #fff;
padding: 20px;
.vp-date-picker_control {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
.date_left,
.date_right {
display: flex;
align-items: center;
span {
font-size: 24px;
cursor: pointer;
&:hover {
color: #409eff;
}
}
}
}
.vp-option-container_inner {
max-height: 204px;
overflow-x: hidden;
}
.vp-date-table {
font-size: 12px;
th {
padding: 5px;
color: #606266;
font-weight: 400;
border: none;
border-bottom: 1px solid #ebeef5;
}
.suffixMonth_td,
.preMonth_td {
color: #c0c4cc;
}
td {
padding: 4 px 0;
box-sizing: border-box;
text-align: center;
cursor: pointer;
position: relative;
border: none;
}
td div {
width: 32px;
height: 32px;
&:hover {
color: #409eff;
}
}
.disabled_day {
background-color: #f5f7fa;
color: #c0c4cc;
cursor: not-allowed;
&:hover {
color: #c0c4cc;
}
}
.select_time_tab {
border-radius: 50%;
color: #ffffff;
background-color: #409eff;
}
.currentTime__tb {
color: #409eff;
}
td .date_table_span {
display: block;
margin: 0 auto;
line-height: 32px;
}
}
}
.vp-option_san {
position: absolute;
width: 0;
height: 0;
border-width: 9px;
z-index: 10000;
border-style: dashed dashed solid;
border-color: transparent transparent #e4e7ed;
font-size: 0;
line-height: 0;
top: 30px;
left: 20px;
&::after {
content: " ";
position: absolute;
width: 0;
height: 0;
border-width: 7px;
z-index: 1;
border-style: dashed dashed solid;
border-color: transparent transparent #ffffff;
font-size: 0;
line-height: 0;
top: -5px;
left: -7px;
}
}
}
</style>
Input 组件代码
<template>
<div class="vp-input">
<div class="vp-input_prefix" v-if="$scopedSlots.prefix">
<slot name="prefix"></slot>
</div>
<input
v-if="type !== 'textarea'"
:class="[
'vp-input-inner',
hasFocus ? 'vp-input-inner_focus' : '',
disabled ? 'input-disabled' : '',
$scopedSlots.prefix ? 'vp-input_inner_prefix' : '',
$scopedSlots.suffix ? 'vp-input_inner_suffix' : '',
$scopedSlots.suffix && clearable
? 'vp-input_inner_suffix_defaultIcon'
: '',
]"
:type="isShowPWD ? 'text' : type"
:placeholder="placeholder"
:value="value"
@input="inputHandle"
:disabled="disabled"
@blur="blurHandle"
@focus="focusHandle"
/>
<textarea
v-else
class="vp-input-textarea"
name=""
id=""
:cols="cols"
:rows="rows"
:value="value"
@input="textareaInputHandle"
:readonly="readonly"
:maxlength="maxlength"
@blur="blurHandle"
@focus="focusHandle"
></textarea>
<div class="vp-input_suffix" v-if="$scopedSlots.suffix">
<slot name="suffix"></slot>
</div>
<span
v-if="type === 'password' && value"
class="iconfont default-icon"
:class="[
isShowPWD ? 'icon-eye' : 'icon-eye1',
$scopedSlots.suffix ? 'default_icon_suffix' : '',
]"
@click="showPWDHandle"
></span>
<span
v-if="clearable && value"
class="iconfont icon-clear_circle_outlined default-icon"
:class="[$scopedSlots.suffix ? 'default_icon_suffix' : '']"
@click="clearHandle"
></span>
</div>
</template>
<script>
export default {
name: "vpInput",
props: {
type: {
type: String,
default: "text",
},
placeholder: {
type: String,
default: "",
},
value: {
type: String | Number,
default: "",
},
disabled: {
type: Boolean,
default: false,
},
clearable: {
type: Boolean,
default: false,
},
cols: {
type: Number,
default: 20,
},
rows: {
type: Number,
default: 5,
},
readonly: {
type: Boolean,
default: false,
},
maxlength: {
type: Number,
default: 100,
},
},
inject: {
vpFormItem: {
default: {},
},
vpForm: {
default: {},
},
},
watch: {
rule(newRule) {
newRule.forEach((item) => {
let trigger = item.trigger;
if (
trigger &&
Object.prototype.toString.call(trigger) === "[object String]"
) {
if (trigger === "input") {
this.inputRule.push(item);
} else if (trigger === "blur") {
this.blurRule.push(item);
}
} else if (
trigger &&
Object.prototype.toString.call(trigger) === "[object Array]"
) {
trigger.forEach((it) => {
if (it === "input") {
this.inputRule.push(item);
} else if (it === "blur") {
this.blurRule.push(item);
}
});
}
});
},
},
data() {
return {
isShowPWD: false,
rule: [],
inputRule: [],
blurRule: [],
ruleMessage: "",
hasFocus: false,
};
},
watch: {
value(newVal) {
this.$emit("input", newVal);
},
},
created() {
if (this.vpForm.rules && this.vpFormItem.prop) {
this.rule = this.vpForm.rules[this.vpFormItem.prop];
}
},
mounted() {},
methods: {
inputHandle(e) {
this.$emit("input", e.target.value, e);
this.$nextTick(() => {
let inputRule = this.inputRule;
if (inputRule) {
inputRule.forEach((rule) => {
if (rule.required) {
if (this.value === "") {
this.ruleMessage = rule.message;
this.$bus.$emit("ruleChange", {
[this.vpFormItem.prop]: {
ruleMessage: this.ruleMessage,
},
});
} else {
this.ruleMessage = "";
this.$bus.$emit("ruleChange", {
[this.vpFormItem.prop]: {
ruleMessage: this.ruleMessage,
},
});
}
}
});
}
});
},
blurHandle(e) {
this.hasFocus = false;
this.$emit("blur", e);
this.$nextTick(() => {
let blurRule = this.blurRule;
if (blurRule) {
blurRule.forEach((rule) => {
if (rule.required) {
if (this.value === "") {
this.ruleMessage = rule.message;
this.$bus.$emit("ruleChange", {
[this.vpFormItem.prop]: {
ruleMessage: this.ruleMessage,
},
});
} else {
if (this.ruleMessage !== "") {
this.ruleMessage = "";
this.$bus.$emit("ruleChange", {
[this.vpFormItem.prop]: {
ruleMessage: this.ruleMessage,
},
});
}
}
}
});
}
});
},
focusHandle(e) {
this.hasFocus = true;
this.$emit("focus", e);
},
showPWDHandle() {
this.isShowPWD = !this.isShowPWD;
},
clearHandle() {
this.$emit("input", "");
},
textareaInputHandle(e) {
this.$emit("input", e.target.value);
},
},
};
</script>
<style lang="less" scoped>
// 默认样式
.vp-input {
width: 100%;
position: relative;
display: flex;
align-items: center;
.default-icon {
cursor: pointer;
position: absolute;
top: 9px;
right: 5px;
}
.vp-input-inner {
box-sizing: border-box;
width: 100%;
height: 35px;
border: 1px solid #dcdfe6;
outline: none;
color: rgb(148, 146, 144);
border-radius: 5px;
border-width: 1px;
padding: 5px 10px;
transition: border 0.2s;
cursor: pointer;
&:hover {
border: 1px solid #c0c4cc;
}
}
.vp-input_inner_prefix {
padding-left: 35px;
}
.vp-input_inner_suffix {
padding-right: 35px;
}
.default_icon_suffix {
right: 40px;
}
.vp-input_suffix {
position: absolute;
display: inline-block;
width: 20px;
padding: 0px 10px;
right: 0;
}
.vp-input_prefix {
position: absolute;
display: inline-block;
width: 20px;
padding: 0px 10px;
left: 0;
}
.vp-input_inner_suffix_defaultIcon {
padding-right: 60px;
}
.vp-input-textarea {
outline: none;
border: 1px solid #dcdfe6;
&:hover {
border: 1px solid #c0c4cc;
}
}
}
.input-disabled {
cursor: not-allowed;
background-color: #f5f7fa;
border-color: #e4e7ed;
color: #c0c4cc;
}
.vp-input-inner_focus {
border: 1px solid #409eff !important;
}
</style>