This commit is contained in:
castleiMac
2022-04-03 20:22:33 +08:00
46 changed files with 1077 additions and 518 deletions

View File

@@ -13,7 +13,7 @@ import {
ButtonSize,
ButtonType,
} from "./interface";
import { BooleanOrString, String } from "../../types";
import { Boolean, BooleanOrString, String } from "../../types";
export interface LayButtonProps {
type?: ButtonType;

View File

@@ -1,211 +1,168 @@
<template>
<div>
<lay-dropdown ref="dropdownRef">
<lay-input :name="name" :value="dateValue" readonly="readonly">
<template #prefix>
<lay-icon type="layui-icon-date"></lay-icon>
</template>
</lay-input>
<template #content>
<!-- 日期选择 -->
<div class="layui-laydate" v-show="showPane === 'date' || showPane === 'datetime'">
<div class="layui-laydate-main laydate-main-list-0">
<div class="layui-laydate-header">
<i
class="layui-icon laydate-icon laydate-prev-y"
@click="changeYearOrMonth('year', -1)"
></i>
<i
class="layui-icon laydate-icon laydate-prev-m"
@click="changeYearOrMonth('month', -1)"
></i>
<div class="laydate-set-ym">
<span @click="showYearPanel">{{ currentYear }} </span>
<span @click="showPane = 'month'">{{ currentMonth + 1 }} </span>
<<<<<<< HEAD <lay-input :name="name" :value="dateValue" readonly="readonly"> ======= <lay-input :name="name"
:value="dateValue || modelValue" readonly> >>>>>>> 4c653112a2fca8ef40349518d79c58ec7105e032 <template #prefix>
<lay-icon type="layui-icon-date"></lay-icon>
</template>
</lay-input>
<template #content>
<!-- 日期选择 -->
<div class="layui-laydate" v-show="showPane === 'date' || showPane === 'datetime'">
<div class="layui-laydate-main laydate-main-list-0">
<div class="layui-laydate-header">
<i class="layui-icon laydate-icon laydate-prev-y" @click="changeYearOrMonth('year', -1)"></i><i
class="layui-icon laydate-icon laydate-prev-m" @click="changeYearOrMonth('month', -1)"></i>
<div class="laydate-set-ym">
<span @click="showYearPanel">{{ currentYear }} </span><span
@click="showPane = 'month'">{{ currentMonth + 1 }} </span>
</div>
<i class="layui-icon laydate-icon laydate-next-m" @click="changeYearOrMonth('month', 1)"></i><i
class="layui-icon laydate-icon laydate-next-y" @click="changeYearOrMonth('year', 1)"></i>
</div>
<i
class="layui-icon laydate-icon laydate-next-m"
@click="changeYearOrMonth('month', 1)"
></i>
<i
class="layui-icon laydate-icon laydate-next-y"
@click="changeYearOrMonth('year', 1)"
></i>
</div>
<div class="layui-laydate-content">
<table>
<thead>
<tr>
<th v-for="item of WEEK_NAME" :key="item">{{ item }}</th>
</tr>
</thead>
<tbody>
<template
v-for="(o, i) of dateList.length % 7 == 0
? dateList.length / 7
: Math.floor(dateList.length / 7) + 1"
:key="i"
>
<div class="layui-laydate-content">
<table>
<thead>
<tr>
<td
v-for="(item, index) of dateList.slice(
<th v-for="item of WEEK_NAME" :key="item">{{ item }}</th>
</tr>
</thead>
<tbody>
<template v-for="(o, i) of dateList.length % 7 == 0
? dateList.length / 7
: Math.floor(dateList.length / 7) + 1" :key="i">
<tr>
<td v-for="(item, index) of dateList.slice(
i * 7,
i * 7 + 7
)"
:key="index"
:data-unix="item.value"
:class="{
'laydate-day-prev': item.type !== 'current',
'layui-this': item.value === currentDay,
}"
@click="handleDayClick(item)"
>{{ item.day }}</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
<div class="layui-laydate-footer">
<span
v-if="type === 'datetime'"
@click="showPane = 'time'"
class="laydate-btns-time"
>选择时间</span>
<div class="laydate-footer-btns">
<span lay-type="clear" class="laydate-btns-clear" @click="clear">清空</span>
<span lay-type="now" class="laydate-btns-now" @click="now">现在</span>
<span lay-type="confirm" class="laydate-btns-confirm" @click="ok">确定</span>
</div>
</div>
</div>
<!-- 年份选择器 -->
<div class="layui-laydate" v-show="showPane === 'year'">
<div class="layui-laydate-main laydate-main-list-0 laydate-ym-show">
<div class="layui-laydate-header">
<div class="laydate-set-ym">
<span class="laydate-time-text">选择年份</span>
)" :key="index" :data-unix="item.value" :class="{
'laydate-day-prev': item.type !== 'current',
'layui-this': item.value === currentDay,
}" @click="handleDayClick(item)">
{{ item.day }}
</td>
</tr>
</template>
</tbody>
</table>
</div>
</div>
<div class="layui-laydate-content" style="height: 220px; overflow-y: auto">
<ul class="layui-laydate-list laydate-year-list">
<li
v-for="item of yearList"
:key="item"
:class="[{ 'layui-this': currentYear === item }]"
@click="handleYearClick(item)"
>{{ item }}</li>
</ul>
<div class="layui-laydate-footer">
<span v-if="type === 'datetime'" @click="showPane = 'time'" class="laydate-btns-time">选择时间</span>
<div class="laydate-footer-btns">
<span lay-type="clear" class="laydate-btns-clear" @click="clear">清空</span><span lay-type="now"
class="laydate-btns-now" @click="now">现在</span><span lay-type="confirm" class="laydate-btns-confirm"
@click="ok">确定</span>
</div>
</div>
</div>
<div class="layui-laydate-footer">
<!-- <span
<!-- 年份选择器 -->
<div class="layui-laydate" v-show="showPane === 'year' || showPane === 'yearmonth'">
<div class="layui-laydate-main laydate-main-list-0 laydate-ym-show">
<div class="layui-laydate-header">
<div class="laydate-set-ym">
<span class="laydate-time-text">选择年份</span>
</div>
</div>
<div class="layui-laydate-content" style="height: 220px; overflow-y: auto">
<ul class="layui-laydate-list laydate-year-list">
<li v-for="item of yearList" :key="item" :class="[{ 'layui-this': currentYear === item }]"
@click="handleYearClick(item)">
{{ item }}
</li>
</ul>
</div>
</div>
<div class="layui-laydate-footer">
<!-- <span
class="layui-laydate-preview"
title="当前选中的结果"
style="color: rgb(102, 102, 102)"
>2022</span>-->
<div class="laydate-footer-btns">
<span lay-type="clear" class="laydate-btns-clear" @click="clear">清空</span>
<span lay-type="now" class="laydate-btns-now" @click="now">现在</span>
<span lay-type="confirm" class="laydate-btns-confirm" @click="ok">确定</span>
</div>
</div>
</div>
<!-- 月份选择器 -->
<div class="layui-laydate" v-show="showPane === 'month'">
<div class="layui-laydate-main laydate-main-list-0 laydate-ym-show">
<div class="layui-laydate-header">
<i
class="layui-icon laydate-icon laydate-prev-y"
@click="changeYearOrMonth('year', -1)"
></i>
<div class="laydate-set-ym">
<span
@click="showYearPanel"
v-if="showPane === 'date' || showPane === 'datetime'"
>{{ currentYear }} 年</span>
<span @click="showPane = 'month'">{{ currentMonth + 1 }} 月</span>
<div class="laydate-footer-btns">
<span lay-type="clear" class="laydate-btns-clear" @click="clear">清空</span><span lay-type="now"
class="laydate-btns-now" @click="now">现在</span><span lay-type="confirm" class="laydate-btns-confirm"
@click="ok">确定</span>
</div>
<i
class="layui-icon laydate-icon laydate-next-y"
@click="changeYearOrMonth('year', 1)"
></i>
</div>
<div class="layui-laydate-content" style="height: 220px">
<ul class="layui-laydate-list laydate-month-list">
<li
v-for="item of MONTH_NAME"
:key="item"
:class="[
{ 'layui-this': MONTH_NAME.indexOf(item) === currentMonth },
]"
@click="handleMonthClick(item)"
>{{ item.slice(0, 3) }}</li>
</ul>
</div>
</div>
<div class="layui-laydate-footer">
<!-- <span
<!-- 月份选择器 -->
<div class="layui-laydate" v-show="showPane === 'month'">
<div class="layui-laydate-main laydate-main-list-0 laydate-ym-show">
<div class="layui-laydate-header">
<i class="layui-icon laydate-icon laydate-prev-y" @click="changeYearOrMonth('year', -1)"></i>
<div class="laydate-set-ym">
<span @click="showYearPanel" v-if="showPane === 'date' || showPane === 'datetime'">{{ currentYear }}
年</span>
<span @click="showPane = 'month'">{{ currentMonth + 1 }} 月</span>
</div>
<i class="layui-icon laydate-icon laydate-next-y" @click="changeYearOrMonth('year', 1)"></i>
</div>
<div class="layui-laydate-content" style="height: 220px">
<ul class="layui-laydate-list laydate-month-list">
<li v-for="item of MONTH_NAME" :key="item" :class="[
{ 'layui-this': MONTH_NAME.indexOf(item) === currentMonth },
]" @click="handleMonthClick(item)">
{{ item.slice(0, 3) }}
</li>
</ul>
</div>
</div>
<div class="layui-laydate-footer">
<!-- <span
class="layui-laydate-preview"
title="当前选中的结果"
style="color: rgb(102, 102, 102)"
>2021-03</span>-->
<div class="laydate-footer-btns">
<span lay-type="clear" class="laydate-btns-clear" @click="clear">清空</span>
<span lay-type="now" class="laydate-btns-now" @click="now">现在</span>
<span lay-type="confirm" class="laydate-btns-confirm" @click="ok">确定</span>
</div>
</div>
</div>
<!-- 时间选择器 -->
<div class="layui-laydate" v-if="showPane == 'time'">
<div class="layui-laydate-main laydate-main-list-0 laydate-time-show">
<div class="layui-laydate-header">
<div class="laydate-set-ym">
<span class="laydate-time-text">选择时间</span>
<div class="laydate-footer-btns">
<span lay-type="clear" class="laydate-btns-clear" @click="clear">清空</span><span lay-type="now"
class="laydate-btns-now" @click="now">现在</span><span lay-type="confirm" class="laydate-btns-confirm"
@click="ok">确定</span>
</div>
</div>
<div class="layui-laydate-content" style="height: 210px">
<ul class="layui-laydate-list laydate-time-list">
<li class="num-list" v-for="item in els" :key="item.type">
<ol class="scroll" @click="choseTime">
<li
v-for="(it, index) in item.count"
:id="item.type + index.toString()"
:data-value="index.toString().padStart(2, '0')"
:data-type="item.type"
:key="it"
:class="[
'num',
index.toString().padStart(2, '0') == hms[item.type]
? 'layui-this'
: '',
]"
>{{ index.toString().padStart(2, "0") }}</li>
</ol>
</li>
</ul>
</div>
<!-- 时间选择器 -->
<div class="layui-laydate" v-if="showPane == 'time'">
<div class="layui-laydate-main laydate-main-list-0 laydate-time-show">
<div class="layui-laydate-header">
<div class="laydate-set-ym">
<span class="laydate-time-text">选择时间</span>
</div>
</div>
<div class="layui-laydate-content" style="height: 210px">
<ul class="layui-laydate-list laydate-time-list">
<li class="num-list" v-for="item in els" :key="item.type">
<ol class="scroll" @click="choseTime">
<li v-for="(it, index) in item.count" :id="item.type + index.toString()"
:data-value="index.toString().padStart(2, '0')" :data-type="item.type" :key="it" :class="[
'num',
index.toString().padStart(2, '0') == hms[item.type]
? 'layui-this'
: '',
]">
{{ index.toString().padStart(2, "0") }}
</li>
</ol>
</li>
</ul>
</div>
</div>
<div class="layui-laydate-footer">
<span @click="showPane = 'date'" v-if="type != 'time'" class="laydate-btns-time">返回日期</span>
<div class="laydate-footer-btns">
<span lay-type="clear" class="laydate-btns-clear" @click="clear">清空</span><span lay-type="now"
class="laydate-btns-now" @click="now">现在</span><span lay-type="confirm" class="laydate-btns-confirm"
@click="ok">确定</span>
</div>
</div>
</div>
<div class="layui-laydate-footer">
<span @click="showPane = 'date'" class="laydate-btns-time">返回日期</span>
<div class="laydate-footer-btns">
<span lay-type="clear" class="laydate-btns-clear" @click="clear">清空</span>
<span lay-type="now" class="laydate-btns-now" @click="now">现在</span>
<span lay-type="confirm" class="laydate-btns-confirm" @click="ok">确定</span>
</div>
</div>
</div>
</template>
</template>
</lay-dropdown>
</div>
</template>
<script lang="ts" setup>
import { computed, nextTick, ref, watch, defineProps, defineEmits } from "vue";
import { ref, watch, computed, defineProps, defineEmits, onMounted } from "vue";
import moment from "moment";
import LayIcon from "../icon/index";
@@ -213,6 +170,17 @@ import LayInput from "../input/index.vue";
import LayDropdown from "../dropdown/index.vue";
import { getDayLength, getYears, getMonth, getYear } from "./day";
export interface LayDatePickerProps {
modelValue?: string;
type?: "date" | "datetime" | "year" | "time" | "month" | "yearmonth";
name?: string;
}
const props = withDefaults(defineProps<LayDatePickerProps>(), {
modelValue: "",
type: "date",
});
const dropdownRef = ref(null);
const $emits = defineEmits(["update:modelValue"]);
@@ -232,24 +200,17 @@ const MONTH_NAME = [
"12月",
];
const hms = ref({ hh: 0, mm: 0, ss: 0 });
const hms = ref({
hh: moment(props.modelValue).hour(),
mm: moment(props.modelValue).minute(),
ss: moment(props.modelValue).second(),
});
const els = [
{ count: 24, type: "hh" },
{ count: 60, type: "mm" },
{ count: 60, type: "ss" },
];
export interface LayDatePickerProps {
modelValue?: string;
type?: "date" | "datetime" | "year" | "time" | "month";
name?: string;
}
const props = withDefaults(defineProps<LayDatePickerProps>(), {
modelValue: "",
type: "date",
});
const currentYear = ref(getYear());
const currentMonth = ref(getMonth());
const currentDay = ref<number>();
@@ -265,14 +226,13 @@ watch(
},
{ immediate: true }
);
// 格式化
const fmtMap = {
date: 'YYYY-MM-DD',
datetime: 'YYYY-MM-DD hh:mm:ss',
year: 'YYYY',
month: 'MM',
'': '',
}
onMounted(() => {
hms.value.hh = moment(props.modelValue).hour();
hms.value.mm = moment(props.modelValue).minute();
hms.value.ss = moment(props.modelValue).second();
});
// 计算结果日期
const dateValue = computed<string>(() => {
if (currentDay.value === -1) {
@@ -347,6 +307,9 @@ const ok = () => {
// 现在时间
const now = () => {
currentDay.value = moment().valueOf();
hms.value.hh = moment().hour();
hms.value.mm = moment().minute();
hms.value.ss = moment().second();
};
// 清空日期
@@ -381,6 +344,9 @@ const handleYearClick = (item: any) => {
currentYear.value = item;
if (props.type === "year") {
currentDay.value = moment().year(item).valueOf();
} else if (props.type === "yearmonth") {
currentDay.value = moment().year(item).valueOf();
showPane.value = "month";
} else {
showPane.value = "date";
}
@@ -390,7 +356,13 @@ const handleYearClick = (item: any) => {
const handleMonthClick = (item: any) => {
currentMonth.value = MONTH_NAME.indexOf(item);
if (props.type === "month") {
currentDay.value = moment().month(MONTH_NAME.indexOf(item)).valueOf();
currentDay.value = moment(currentDay.value)
.month(MONTH_NAME.indexOf(item))
.valueOf();
} else if (props.type === "yearmonth") {
currentDay.value = moment(currentDay.value)
.month(MONTH_NAME.indexOf(item))
.valueOf();
} else {
showPane.value = "date";
}
@@ -410,7 +382,6 @@ const choseTime = (e: any) => {
if (e.target.nodeName == "LI") {
let { value, type } = e.target.dataset;
hms.value[type as keyof typeof hms.value] = value;
e.target.scrollIntoView({ behavior: "smooth" });
}
};
</script>

View File

@@ -20,3 +20,7 @@
.layui-empty-description {
margin: 0;
}
.layui-empty-extra {
margin-top: 30px;
}

View File

@@ -24,5 +24,8 @@ const props = withDefaults(defineProps<LayEmptyProps>(), {
<img class="layui-empty-image-default" src="./index.svg" />
</div>
<div class="layui-empty-description">{{ description }}</div>
<div class="layui-empty-extra">
<slot name="extra"></slot>
</div>
</div>
</template>

View File

@@ -35,8 +35,8 @@ const props = withDefaults(defineProps<LayDropdownProps>(), {
<div class="layui-exception-details-content">
<div class="layui-exception-details-title">{{ title }}</div>
<div class="layui-exception-details-describe">{{ describe }}</div>
<div class="layui-exception-details-operate">
<slot name="action"></slot>
<div class="layui-exception-details-extra">
<slot name="extra"></slot>
</div>
</div>
</div>

View File

@@ -32,7 +32,6 @@ const props = withDefaults(defineProps<LayMenuProps>(), {
collapseTransition: true,
});
const isTree = computed(() => props.tree);
const isCollapse = computed(() => props.collapse);
const isCollapseTransition = computed(() => props.collapseTransition);
@@ -67,7 +66,8 @@ watch(
// 赋值所有打开
emit("update:openKeys", oldOpenKeys.value);
}
}, { immediate: true }
},
{ immediate: true }
);
provide("isTree", isTree);

View File

@@ -26,10 +26,10 @@
margin-top: 20px;
width: 80%;
border-radius: 10px;
background-color: whitesmoke;
background-color: white;
margin-left: 10%;
}
.result .action {
.result .extra {
padding-top: 10px;
border-top: 1px whitesmoke solid;
margin-top: 25px;

View File

@@ -71,8 +71,8 @@ const props = withDefaults(defineProps<LayResultProps>(), {
<div class="content">
<slot name="content"></slot>
</div>
<div class="action">
<slot name="action"></slot>
<div class="extra">
<slot name="extra"></slot>
</div>
</div>
</template>

View File

@@ -29,7 +29,9 @@ const isTree: Ref<boolean> = inject("isTree") as Ref<boolean>;
const selectedKey: Ref<string> = inject("selectedKey") as Ref<string>;
const openKeys: Ref<string[]> = inject("openKeys") as Ref<string[]>;
const isCollapse: Ref<boolean> = inject("isCollapse") as Ref<boolean>;
const isCollapseTransition: Ref<boolean> = inject("isCollapseTransition") as Ref<boolean>;
const isCollapseTransition: Ref<boolean> = inject(
"isCollapseTransition"
) as Ref<boolean>;
const isOpen = computed(() => {
return openKeys.value.includes(props.id);

View File

@@ -10,26 +10,35 @@ import "./index.less";
export interface LaySwitchProps {
disabled?: boolean;
modelValue?: boolean;
modelValue?: string | number | boolean;
onswitchText?: string;
unswitchText?: string;
onswitchColor?: string;
unswitchColor?: string;
onswitchValue?: string | number | boolean;
unswitchValue?: string | number | boolean;
}
const props = withDefaults(defineProps<LaySwitchProps>(), {
disabled: false,
onswitchValue: true,
unswitchValue: false,
});
const emit = defineEmits(["update:modelValue", "change"]);
const isActive = computed({
get() {
return props.modelValue;
return props.modelValue === props.onswitchValue;
},
set(val) {
emit("change", val);
emit("update:modelValue", val);
if (val) {
emit("change", props.onswitchValue);
emit("update:modelValue", props.onswitchValue);
} else {
emit("change", props.unswitchValue);
emit("update:modelValue", props.unswitchValue);
}
},
});

View File

@@ -9,6 +9,22 @@
overflow: hidden;
}
.layui-tab.is-right {
display: flex;
flex-direction: row-reverse;
justify-content: space-between
}
.layui-tab.is-bottom {
display: flex;
flex-direction: column-reverse
}
.layui-tab-head {
display: inline-block;
background-color: @global-neutral-color-1;
}
.layui-tab-title {
position: relative;
left: 0;
@@ -37,15 +53,17 @@
cursor: pointer;
}
/** 位置开始 */
.layui-tab.is-right {
display: flex;
flex-direction: row-reverse;
justify-content: space-between
.layui-tab-title li a {
display: block;
padding: 0 15px;
margin: 0 -15px;
}
.layui-tab-head{
display: inline-block;
.layui-tab-head.is-top,
.layui-tab-head.is-bottom,
.layui-tab-title.is-top,
.layui-tab-title.is-bottom {
width: 100%;
}
.layui-tab-title.is-right,
@@ -63,48 +81,9 @@
display: list-item;
}
.layui-tab-head.is-right + .layui-tab-content,
.layui-tab-head.is-left + .layui-tab-content {
height: 100%;
padding: 0 10px;
display: inline-block;
vertical-align: top;
}
.layui-tab-brief > .layui-tab-head.is-left > .layui-tab-more li.layui-this:after,
.layui-tab-brief > .layui-tab-head.is-left > .layui-tab-title .layui-this:after {
border: none;
border-radius: 0;
border-right: 2px solid @global-checked-color;
margin-left: 1px;
}
.layui-tab-brief > .layui-tab-head.is-right > .layui-tab-more li.layui-this:after,
.layui-tab-brief > .layui-tab-head.is-right > .layui-tab-title .layui-this:after {
border: none;
border-radius: 0;
border-left: 2px solid @global-checked-color;
margin-right: 1px;
}
.layui-tab-brief > .layui-tab-head.is-right > .layui-tab-title{
border-left: 1px solid @global-neutral-color-3;
}
.layui-tab-brief > .layui-tab-head.is-left > .layui-tab-title{
border-right: 1px solid @global-neutral-color-3;
}
/** 位置结束*/
.layui-tab-title li a {
display: block;
padding: 0 15px;
margin: 0 -15px;
}
.layui-tab-title .layui-this {
color: #000;
background-color: #fff
}
.layui-tab-title .layui-this:after {
@@ -132,6 +111,101 @@
border-left-color: #FFF;
}
.layui-tab-brief>.layui-tab-head{
background-color: #FFF;
}
.layui-tab-brief>.layui-tab-head>.layui-tab-title .layui-this {
color: @global-primary-color;
}
.layui-tab-brief>.layui-tab-head>.layui-tab-more li.layui-this:after,
.layui-tab-brief>.layui-tab-head>.layui-tab-title .layui-this:after {
border: none;
border-radius: 0;
border-bottom: 2px solid @global-checked-color;
}
.layui-tab-brief>.layui-tab-head.is-right>.layui-tab-title,
.layui-tab-brief>.layui-tab-head.is-left>.layui-tab-title {
border-right: 1px solid @global-neutral-color-3;
}
.layui-tab-brief[overflow]>.layui-tab-head>.layui-tab-title .layui-this:after {
top: -1px;
}
.layui-tab-brief > .layui-tab-head.is-left > .layui-tab-more li.layui-this:after,
.layui-tab-brief > .layui-tab-head.is-left > .layui-tab-title .layui-this:after {
border: none;
border-radius: 0;
border-right: 2px solid @global-checked-color;
margin-left: 1px;
}
.layui-tab-brief > .layui-tab-head.is-right > .layui-tab-more li.layui-this:after,
.layui-tab-brief > .layui-tab-head.is-right > .layui-tab-title .layui-this:after {
border: none;
border-radius: 0;
border-left: 2px solid @global-checked-color;
margin-right: 1px;
}
.layui-tab-card {
border-width: 1px;
border-style: solid;
border-radius: 2px;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.1);
}
.layui-tab-card>.layui-tab-head>.layui-tab-title li {
margin-right: -1px;
margin-left: -1px;
}
.layui-tab-card>.layui-tab-head>.layui-tab-title.is-left li,
.layui-tab-card>.layui-tab-head>.layui-tab-title.is-right li {
margin-top: -1px;
margin-bottom: -1px;
}
.layui-tab-card>.layui-tab-head>.layui-tab-title.is-top .layui-this:after {
border: 1px solid @global-neutral-color-3;
border-bottom-color: #fff;
}
.layui-tab-card>.layui-tab-head>.layui-tab-title.is-bottom .layui-this:after {
border: 1px solid @global-neutral-color-3;
border-top-color: #fff;
}
.layui-tab-card>.layui-tab-head>.layui-tab-title.is-left .layui-this:after {
border: 1px solid @global-neutral-color-3;
border-right-color: #fff;
}
.layui-tab-card>.layui-tab-head>.layui-tab-title.is-right .layui-this:after {
border: 1px solid @global-neutral-color-3;
border-left-color: #fff;
}
.layui-tab-card>.layui-tab-head>.layui-tab-title .layui-tab-bar {
height: 40px;
line-height: 40px;
border-radius: 0;
border-top: none;
border-right: none;
}
.layui-tab-card>.layui-tab-more .layui-this {
background: 0 0;
color: @global-checked-color;
}
.layui-tab-card>.layui-tab-more .layui-this:after {
border: none;
}
.layui-tab-bar {
position: absolute;
right: 0;
@@ -182,10 +256,6 @@
top: -2px\0 / IE9;
}
.layui-tab-content {
padding: 15px 0;
}
.layui-tab-title li .layui-tab-close {
position: relative;
display: inline-block;
@@ -207,87 +277,14 @@
color: #fff;
}
.layui-tab-brief > .layui-tab-head > .layui-tab-title .layui-this {
color: @global-primary-color;
.layui-tab-content {
padding: 15px 0;
}
.layui-tab-brief > .layui-tab-head > .layui-tab-more li.layui-this:after,
.layui-tab-brief > .layui-tab-head > .layui-tab-title .layui-this:after {
border: none;
border-radius: 0;
border-bottom: 2px solid @global-checked-color;
}
.layui-tab-brief[overflow] > .layui-tab-head > .layui-tab-title .layui-this:after {
top: -1px;
}
.layui-tab-card {
border-width: 1px;
border-style: solid;
border-radius: 2px;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.1);
}
.layui-tab-card > .layui-tab-head.is-top,
.layui-tab-card > .layui-tab-head.is-bottom {
width: 100%;
}
.layui-tab-card > .layui-tab-head > .layui-tab-title.is-top,
.layui-tab-card > .layui-tab-head > .layui-tab-title.is-bottom {
width: 100%;
}
.layui-tab-card > .layui-tab-head > .layui-tab-title {
background-color: @global-neutral-color-1;
}
.layui-tab-card > .layui-tab-head > .layui-tab-title li {
margin-right: -1px;
margin-left: -1px;
}
.layui-tab-card > .layui-tab-head > .layui-tab-title.is-left li,
.layui-tab-card > .layui-tab-head > .layui-tab-title.is-right li{
margin-top: -1px;
margin-bottom: -1px;
}
.layui-tab-card > .layui-tab-head > .layui-tab-title .layui-this {
background-color: #fff;
}
.layui-tab-card > .layui-tab-head > .layui-tab-title.is-top .layui-this:after {
border: 1px solid @global-neutral-color-3;
border-bottom-color: #fff;
}
.layui-tab-card > .layui-tab-head > .layui-tab-title.is-bottom .layui-this:after {
border: 1px solid @global-neutral-color-3;
border-top-color: #fff;
}
.layui-tab-card > .layui-tab-head > .layui-tab-title.is-left .layui-this:after {
border: 1px solid @global-neutral-color-3;
border-right-color: #fff;
}
.layui-tab-card > .layui-tab-head > .layui-tab-title.is-right .layui-this:after {
border: 1px solid @global-neutral-color-3;
border-left-color: #fff;
}
.layui-tab-card > .layui-tab-head > .layui-tab-title .layui-tab-bar {
height: 40px;
line-height: 40px;
border-radius: 0;
border-top: none;
border-right: none;
}
.layui-tab-card > .layui-tab-more .layui-this {
background: 0 0;
color: @global-checked-color;
}
.layui-tab-card > .layui-tab-more .layui-this:after {
border: none;
.layui-tab.is-right>.layui-tab-content,
.layui-tab.is-left>.layui-tab-content {
height: 100%;
padding: 0 10px;
display: inline-block;
vertical-align: top;
}

View File

@@ -104,10 +104,6 @@ provide("slotsChange", slotsChange);
]"
v-if="active"
>
<div v-if="tabPosition === 'bottom'" class="layui-tab-content">
<slot></slot>
</div>
<div
:class="['layui-tab-head', props.tabPosition ? `is-${tabPosition}` : '']"
>
@@ -133,7 +129,7 @@ provide("slotsChange", slotsChange);
</ul>
</div>
<div v-if="tabPosition != 'bottom'" class="layui-tab-content">
<div class="layui-tab-content">
<slot></slot>
</div>
</div>

View File

@@ -59,3 +59,42 @@
.layui-timeline-item:before {
background-color: #eee;
}
.layui-timeline-horizontal .layui-timeline-item {
display: inline-block;
width: 25%;
text-align: center;
padding-top: 10px;
vertical-align: top;
}
.layui-timeline-horizontal .layui-timeline-axis {
left: 47%;
top: -4px;
}
.layui-timeline-horizontal .layui-timeline-item:before {
left: 0px;
top: 5px;
width: 100%;
height: 1px;
}
.layui-timeline-horizontal .layui-timeline-item:first-child:before {
display: block;
}
.layui-timeline-horizontal .layui-timeline-item:last-child:before {
display: block;
}
.layui-timeline-horizontal .layui-timeline-content {
padding: 15px;
}
.layui-timeline-horizontal .layui-timeline-title {
text-align: center;
position: relative;
margin-bottom: 10px;
line-height: 22px;
}

View File

@@ -6,10 +6,24 @@ export default {
<script setup lang="ts">
import "./index.less";
import { computed, withDefaults } from "vue";
export interface LayTimelineProps {
direction: "horizontal" | "vertical";
}
const props = withDefaults(defineProps<LayTimelineProps>(), {
direction: "vertical",
});
const timeLineClass = computed(() => [
"layui-timeline",
props.direction === "horizontal" ? "layui-timeline-horizontal" : "",
]);
</script>
<template>
<ul class="layui-timeline">
<ul :class="timeLineClass">
<slot></slot>
</ul>
</template>

View File

@@ -17,8 +17,8 @@ export default {
</script>
<script setup lang="ts">
import LayCollapseTransition from "./collapseTransition.vue";
import LayFadeTransition from "./fadeTransition.vue";
import LayCollapseTransition from "./transitions/collapseTransition.vue";
import LayFadeTransition from "./transitions/fadeTransition.vue";
export interface LayTransitionProps {
type?: string;

9
src/component/upload/cropper.min.css vendored Normal file
View File

@@ -0,0 +1,9 @@
/*!
* Cropper.js v1.5.12
* https://fengyuanchen.github.io/cropperjs
*
* Copyright 2015-present Chen Fengyuan
* Released under the MIT license
*
* Date: 2021-06-12T08:00:11.623Z
*/.cropper-container{direction:ltr;font-size:0;line-height:0;position:relative;-ms-touch-action:none;touch-action:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.cropper-container img{image-orientation:0deg;display:block;height:100%;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{bottom:0;left:0;position:absolute;right:0;top:0}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline:1px solid #39f;outline-color:rgba(51,153,255,.75);overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:33.33333%;left:0;top:33.33333%;width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:33.33333%;top:0;width:33.33333%}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center:after,.cropper-center:before{background-color:#eee;content:" ";display:block;position:absolute}.cropper-center:before{height:1px;left:-3px;top:0;width:7px}.cropper-center:after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}@media (min-width:768px){.cropper-point.point-se{height:15px;width:15px}}@media (min-width:992px){.cropper-point.point-se{height:10px;width:10px}}@media (min-width:1200px){.cropper-point.point-se{height:5px;opacity:.75;width:5px}}.cropper-point.point-se:before{background-color:#39f;bottom:-50%;content:" ";display:block;height:200%;opacity:0;position:absolute;right:-50%;width:200%}.cropper-invisible{opacity:0}.cropper-bg{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC")}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}

View File

@@ -1,5 +1,5 @@
@import (reference) "../../theme/variable.less";
@import "./cropper.min.css";
.layui-upload-file {
// display: none !important;
opacity: 0.01;
@@ -85,3 +85,17 @@
.layui-btn-container .layui-upload-choose {
padding-left: 0;
}
.layui-upload-drag-disable{
opacity:0.8;
z-index:1;
cursor: not-allowed;
}
.copper-container{
// width:1000px;
}
._lay_upload_img{
display: block;
max-width: 100%;
}

View File

@@ -7,11 +7,49 @@ export default {
import "./index.less";
import { Recordable } from "../../types";
import { layer } from "@layui/layer-vue";
import { ref, useSlots, withDefaults } from "vue";
import {
computed,
ComputedRef,
getCurrentInstance,
nextTick,
ref,
toRaw,
useSlots,
withDefaults,
} from "vue";
import { templateRef } from "@vueuse/core";
import { LayLayer } from "@layui/layer-vue";
import Cropper from "cropperjs";
// 组件的参数字段类型
//https://www.layuiweb.com/doc/modules/upload.html#options
export interface LayerButton {
text: string;
callback: Function;
}
export interface LayerModal {
title?: string;
resize?: boolean;
move?: boolean;
maxmin?: boolean;
offset?: string[];
content?: string;
shade?: boolean;
shadeClose?: boolean;
shadeOpacity?: number;
zIndex?: number;
type?: "component" | "iframe";
closeBtn?: boolean;
area: string[];
btn?: LayerButton[];
btnAlign?: "l" | "r" | "c";
anim?: boolean;
isOutAnim?: boolean;
}
export interface cutOptions {
layerOption: LayerModal;
copperOption?: typeof Cropper;
}
export interface LayUploadProps {
url?: string;
data?: any;
@@ -22,8 +60,50 @@ export interface LayUploadProps {
multiple?: boolean;
number?: number;
drag?: boolean;
disabled?: boolean;
cut?: boolean;
cutOptions: cutOptions;
}
const getCutDownResult = () => {
if (_cropper) {
const canvas = _cropper.getCroppedCanvas();
let imgData = canvas.toDataURL('"image/png"');
let currentTimeStamp = new Date().valueOf();
emit("cutdone", Object.assign({ currentTimeStamp, msg: imgData }));
let newFile = dataURLtoFile(imgData);
commonUploadTransaction([newFile]);
nextTick(() => clearAllCutEffect());
} else {
errorF(cutInitErrorMsg);
}
};
const closeCutDownModal = () => {
let currentTimeStamp = new Date().valueOf();
emit("cutcancel", Object.assign({ currentTimeStamp }));
nextTick(() => clearAllCutEffect());
};
const clearAllCutEffect = () => {
activeUploadFiles.value = [];
activeUploadFilesImgs.value = [];
innerCutVisible.value = false;
};
let defaultCutLayerOption: LayerModal = {
title: "标题",
move: true,
maxmin: false,
offset: [],
btn: [
{ text: "导出", callback: getCutDownResult },
{ text: "取消", callback: closeCutDownModal },
],
area: ["640px", "640px"],
content: "11",
shade: true,
shadeClose: true,
type: "component",
};
const props = withDefaults(defineProps<LayUploadProps>(), {
acceptMime: "images",
field: "file",
@@ -31,22 +111,48 @@ const props = withDefaults(defineProps<LayUploadProps>(), {
multiple: false,
number: 0,
drag: false,
disabled: false,
cut: false,
cutOptions: void 0,
});
const slot = useSlots();
const slots = slot.default && slot.default();
const emit = defineEmits(["choose", "before", "done", "error"]);
const context = getCurrentInstance();
const emit = defineEmits([
"choose",
"before",
"done",
"error",
"cutdone",
"cutcancel",
]);
// 内部变量
const isDragEnter = ref(false);
// 待处理的上传文件
const activeUploadFiles = ref<any[]>([]);
// 待处理的上传图片
const activeUploadFilesImgs = ref<any[]>([]);
const orgFileInput = templateRef<HTMLElement>("orgFileInput");
let _cropper: any = null;
let computedCutLayerOption: ComputedRef<LayerModal>;
if (props.cutOptions && props.cutOptions.layerOption) {
computedCutLayerOption = computed(() =>
Object.assign(defaultCutLayerOption, props.cutOptions.layerOption)
);
} else {
computedCutLayerOption = computed(() => defaultCutLayerOption);
}
// 统一异常提示的常量
const defaultErrorMsg = "上传失败";
const urlErrorMsg = "上传地址格式不合法";
const numberErrorMsg = "文件上传超过规定的个数";
const sizeErrorMsg = "文件大小超过限制";
const uploadRemoteErrorMsg = "请求上传接口出现异常";
const cutInitErrorMsg = "剪裁插件初始化失败";
// 统一成功提示
const uploadSuccess = "上传成功";
//内部方法 -> start
@@ -56,6 +162,8 @@ interface localUploadTransaction {
files: File[] | Blob[];
[propMame: string]: any;
}
const innerCutVisible = ref<boolean>(false);
const localUploadTransaction = (option: localUploadTransaction) => {
const { url, files } = option;
let formData = new FormData();
@@ -88,6 +196,20 @@ interface localUploadOption {
url: string;
[propMame: string]: any;
}
const dataURLtoFile = (dataurl: string) => {
let arr: any[] = dataurl.split(",");
let mime: string = "";
if (arr.length > 0) {
mime = arr[0].match(/:(.*?);/)[1];
}
let bstr = atob(arr[1]);
let n = bstr.length;
let u8arr = new Uint8Array(n);
while (n--) {
u8arr[n] = bstr.charCodeAt(n);
}
return new Blob([u8arr], { type: mime });
};
const errorF = (errorText: string) => {
let currentTimeStamp = new Date().valueOf();
@@ -139,7 +261,13 @@ const localUpload = (option: localUploadOption, callback: Function) => {
cb();
}
};
const filetoDataURL = (file: File, fn: Function) => {
const reader = new FileReader();
reader.onloadend = function (e: any) {
fn(e.target.result);
};
reader.readAsDataURL(file);
};
const getUploadChange = (e: any) => {
const files = e.target.files;
const _files = [...files];
@@ -165,17 +293,43 @@ const getUploadChange = (e: any) => {
_sizeErrorFile.name
} ${sizeErrorMsg},文件最大不可超过${props.size * 1000}kb`;
errorF(errorMsg);
return;
}
}
}
for (let item of _files) {
activeUploadFiles.value.push(item);
filetoDataURL(item, function (res: any) {
activeUploadFilesImgs.value.push(res);
});
}
let arm1 = props.cut && props.acceptMime == "images" && !props.multiple;
let arm2 = props.cut && props.acceptMime == "images" && props.multiple;
if (arm1) {
innerCutVisible.value = true;
setTimeout(() => {
let _imgs = document.getElementsByClassName("_lay_upload_img");
let _img = _imgs[0];
_cropper = new Cropper(_img, {
aspectRatio: 16 / 9,
});
}, 400);
} else {
if (arm2) {
console.warn(
"layui-vue:当前版本暂不支持单次多文件剪裁,尝试设置 multiple 为false,通过@done获取返回文件对象"
);
}
commonUploadTransaction(_files);
}
};
const commonUploadTransaction = (_files: any[]) => {
if (props.url) {
// 表单提交
localUploadTransaction({
url: props.url,
files: _files,
});
} else {
// 抛出上传文件信息
emit("done", _files);
}
};
@@ -188,21 +342,9 @@ const chooseFile = () => {
};
const clickOrgInput = () => {
let currentTimeStamp = new Date().valueOf();
//console.log(currentTimeStamp);
emit("choose", currentTimeStamp);
};
const uploadDragOver = (e: any) => {};
const uploadDragDrop = (e: any) => {
isDragEnter.value = false;
console.log(e);
};
const uploadDragStop = (e: any) => {};
const uploadDragEnter = (e: any) => {
isDragEnter.value = true;
};
const uploadDragLeave = (e: any) => {
isDragEnter.value = false;
};
const cutTransaction = () => {};
//内部方法 -> end
</script>
<template>
@@ -216,11 +358,12 @@ const uploadDragLeave = (e: any) => {
:name="field"
@change="getUploadChange"
:field="field"
:disabled="disabled"
ref="orgFileInput"
/>
<div v-if="!drag">
<div class="layui-upload-btn-box">
<lay-button type="primary" @click.stop="chooseFile"
<lay-button type="primary" @click.stop="chooseFile" :disabled="disabled"
>上传图片</lay-button
>
</div>
@@ -228,11 +371,13 @@ const uploadDragLeave = (e: any) => {
<div
v-else
class="layui-upload-drag"
:class="isDragEnter ? 'layui-upload-drag-draging' : ''"
@dragleave.stop="uploadDragLeave"
@dragenter.stop="uploadDragEnter"
@dragover.stop="uploadDragOver"
@drop="uploadDragDrop"
:class="
disabled
? 'layui-upload-drag-disable'
: isDragEnter
? 'layui-upload-drag-draging'
: ''
"
@click.stop="chooseFile"
>
<i class="layui-icon"></i>
@@ -242,6 +387,34 @@ const uploadDragLeave = (e: any) => {
<img src="" alt="上传成功后渲染" style="max-width: 196px" />
</div>
</div>
<lay-layer
:title="computedCutLayerOption.title"
:move="computedCutLayerOption.move"
:resize="computedCutLayerOption.resize"
:shade="computedCutLayerOption.shade"
:shadeClose="computedCutLayerOption.shadeClose"
:shadeOpacity="computedCutLayerOption.shadeOpacity"
:zIndex="computedCutLayerOption.zIndex"
:btnAlign="computedCutLayerOption.btnAlign"
:area="computedCutLayerOption.area"
:anim="computedCutLayerOption.anim"
:isOutAnim="computedCutLayerOption.isOutAnim"
:btn="computedCutLayerOption.btn"
v-model="innerCutVisible"
@close="clearAllCutEffect"
>
<div
class="copper-container"
v-for="(base64str, index) in activeUploadFilesImgs"
:key="`file${index}`"
>
<img
:src="base64str"
:id="`_lay_upload_img${index}`"
class="_lay_upload_img"
/>
</div>
</lay-layer>
<div class="layui-upload-list">
<slot name="preview"></slot>
</div>

View File

@@ -46,8 +46,12 @@ const changeTheme = (theme: string) => {
brightness: 100,
contrast: 90,
sepia: 0,
// darkSchemeTextColor: 'rgba(255, 255, 255, 0.9)',
// darkSchemeBackgroundColor: '#22272E'
darkSchemeTextColor: getComputedStyle(
document.documentElement
).getPropertyValue("--global-dark-text-color"),
darkSchemeBackgroundColor: getComputedStyle(
document.documentElement
).getPropertyValue("--global-dark-background-color"),
},
{
invert: [],
@@ -60,8 +64,10 @@ const changeTheme = (theme: string) => {
disableStyleSheetsProxy: false,
}
);
localStorage.setItem("layui-vue-theme-dark", "true");
} else {
disableDarkMode();
localStorage.setItem("layui-vue-theme-dark", "false");
}
};

View File

@@ -60,4 +60,8 @@
--global-neutral-color-8: #c2c2c2;
--global-dark-text-color: #FFFFFFc9;
--global-dark-background-color: #22272E;
}

View File

@@ -1,13 +0,0 @@
import { useFetch } from "@vueuse/core";
export function getLayuiVueVersion() {
const { data } = useFetch(
`https://data.jsdelivr.com/v1/package/npm/@layui/layui-vue`,
{
timeout: 1000 * 2,
initialData: "",
afterFetch: (ctx) => ((ctx.data = ctx.data.tags.latest), ctx),
}
).json();
return data;
}