init: v3 init
This commit is contained in:
@@ -1,210 +0,0 @@
|
||||
<template>
|
||||
<div class="detail__wrap">
|
||||
<div v-if="title" class="detail__wrap--header" @click="toggleClick">
|
||||
<div class="detail__wrap--title">
|
||||
<div>
|
||||
{{ title }}
|
||||
<el-tooltip v-if="message" :content="message" placement="right">
|
||||
<i class="el-icon-warning-outline"></i>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<i
|
||||
v-if="collapsed"
|
||||
:class="['el-icon-arrow-down', { 'el-icon-arrow-down-transform': !show }]"
|
||||
></i>
|
||||
</div>
|
||||
<el-collapse-transition>
|
||||
<div v-show="show" class="detail__content" :style="contentStyleObj">
|
||||
<el-row>
|
||||
<el-col v-for="(item, $index) in schema" :key="$index" :span="item.span || 12">
|
||||
<div class="detail__content--item" :class="{ 'detail__content--flex': !vertical }">
|
||||
<div class="content__item--label" :style="labelStyleObj">
|
||||
<slot :name="item.field" :row="item">
|
||||
{{ item.label }}
|
||||
</slot>
|
||||
</div>
|
||||
<div class="content__item--message" :style="messageStyleObj">
|
||||
<slot :name="`${item.field}Content`" :row="data">
|
||||
{{ data[item.field] }}
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-collapse-transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ComDetail">
|
||||
import { PropType, ref, computed } from 'vue'
|
||||
import { SchemaConfig } from './types'
|
||||
|
||||
const props = defineProps({
|
||||
// 详情标题
|
||||
title: {
|
||||
type: String as PropType<string>,
|
||||
default: ''
|
||||
},
|
||||
// 是否可折叠
|
||||
collapsed: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true
|
||||
},
|
||||
// 辅助提示
|
||||
message: {
|
||||
type: String as PropType<string>,
|
||||
default: ''
|
||||
},
|
||||
// 是否需要边框
|
||||
border: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true
|
||||
},
|
||||
// 需要展示的数据
|
||||
data: {
|
||||
type: Object as PropType<IObj>,
|
||||
default: () => {
|
||||
return {}
|
||||
},
|
||||
required: true
|
||||
},
|
||||
// 布局展示的数据
|
||||
schema: {
|
||||
type: Array as PropType<SchemaConfig[]>,
|
||||
default: () => [],
|
||||
required: true
|
||||
},
|
||||
// 是否标题和内容各占一行 垂直布局
|
||||
vertical: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false
|
||||
},
|
||||
// 标题宽度
|
||||
labelWidth: {
|
||||
type: String as PropType<string>,
|
||||
default: '150px'
|
||||
},
|
||||
// 标题位置
|
||||
labelAlign: {
|
||||
type: String as PropType<string>,
|
||||
default: 'left'
|
||||
},
|
||||
// 边框颜色
|
||||
borderColor: {
|
||||
type: String as PropType<string>,
|
||||
default: '#f0f0f0'
|
||||
},
|
||||
// 标题背景颜色
|
||||
labelBg: {
|
||||
type: String as PropType<string>,
|
||||
default: '#fafafa'
|
||||
},
|
||||
classic: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const show = ref<boolean>(true)
|
||||
const contentStyleObj = computed(() => {
|
||||
return {
|
||||
borderTop: props.border ? `1px solid ${props.borderColor}` : '',
|
||||
borderLeft: props.border ? `1px solid ${props.borderColor}` : ''
|
||||
}
|
||||
})
|
||||
const labelStyleObj = computed((): any => {
|
||||
return {
|
||||
width: props.vertical ? `100%` : props.labelWidth,
|
||||
textAlign: props.labelAlign,
|
||||
backgroundColor: props.border && !props.classic ? props.labelBg : '',
|
||||
borderRight: props.border && !props.classic ? `1px solid ${props.borderColor}` : '',
|
||||
borderBottom: props.border && !props.classic ? `1px solid ${props.borderColor}` : ''
|
||||
}
|
||||
})
|
||||
const messageStyleObj = computed(() => {
|
||||
return {
|
||||
width: props.vertical ? `100%` : `calc(100% - ${props.labelWidth})`,
|
||||
borderRight: props.border && !props.classic ? `1px solid ${props.borderColor}` : '',
|
||||
borderBottom: props.border && !props.classic ? `1px solid ${props.borderColor}` : ''
|
||||
}
|
||||
})
|
||||
|
||||
function toggleClick() {
|
||||
if (props.collapsed) {
|
||||
show.value = !show.value
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.detail__wrap {
|
||||
padding: 10px;
|
||||
background: #fff;
|
||||
border-radius: 2px;
|
||||
|
||||
.detail__wrap--header {
|
||||
display: flex;
|
||||
height: 32px;
|
||||
margin-bottom: 10px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
// cursor: pointer;
|
||||
.detail__wrap--title {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
font-family: MicrosoftYaHei-Bold;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: -10px;
|
||||
width: 4px;
|
||||
height: 90%;
|
||||
background: var(--main-color);
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
|
||||
.el-icon-arrow-down {
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.el-icon-arrow-down-transform {
|
||||
transform: rotate(-180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.detail__content {
|
||||
:deep(.el-row) {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.detail__content--flex {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.content__item--label {
|
||||
padding: 8px 16px;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #918e8d;
|
||||
}
|
||||
|
||||
.content__item--message {
|
||||
padding: 8px 16px;
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: #606266;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +0,0 @@
|
||||
export interface SchemaConfig {
|
||||
field: string // 字段名
|
||||
label?: string // label名
|
||||
span?: number // 列的数量
|
||||
}
|
||||
@@ -1,198 +0,0 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
ref="dialogRef"
|
||||
v-bind="getBindValue"
|
||||
:fullscreen="fullscreen"
|
||||
destroy-on-close
|
||||
lock-scroll
|
||||
:close-on-click-modal="false"
|
||||
top="10vh"
|
||||
>
|
||||
<template #title>
|
||||
<slot name="title">
|
||||
{{ title }}
|
||||
</slot>
|
||||
<svg-icon
|
||||
v-if="showFullscreen"
|
||||
:icon-class="fullscreen ? 'exit-fullscreen' : 'fullscreen'"
|
||||
class-name="dialog__icon"
|
||||
@click="toggleFull"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 弹窗内容 -->
|
||||
<el-scrollbar
|
||||
:class="
|
||||
fullscreen && slots.footer
|
||||
? 'com-dialog__content--footer'
|
||||
: fullscreen && !slots.footer
|
||||
? 'com-dialog__content--fullscreen'
|
||||
: 'com-dialog__content'
|
||||
"
|
||||
>
|
||||
<div class="content__wrap">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
|
||||
<template v-if="slots.footer" #footer>
|
||||
<slot name="footer"></slot>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ComDialog">
|
||||
import { ref, computed, PropType, nextTick, unref, useAttrs, useSlots } from 'vue'
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||
|
||||
const slots = useSlots()
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String as PropType<string>,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示全屏按钮
|
||||
showFullscreen: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true
|
||||
},
|
||||
// 是否可以拖拽
|
||||
draggable: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
const dialogRef = ref<HTMLElement | null>(null)
|
||||
|
||||
const fullscreen = ref<boolean>(false)
|
||||
|
||||
const getBindValue = computed((): any => {
|
||||
const delArr: string[] = ['showFullscreen', 'draggable']
|
||||
const attrs = useAttrs()
|
||||
const obj = { ...attrs, ...props }
|
||||
for (const key in obj) {
|
||||
if (delArr.indexOf(key) !== -1) {
|
||||
delete obj[key]
|
||||
}
|
||||
}
|
||||
return obj
|
||||
})
|
||||
|
||||
function toggleFull(): void {
|
||||
fullscreen.value = !fullscreen.value
|
||||
// 全屏的时候需要重新定义left top
|
||||
if (fullscreen.value && props.draggable) {
|
||||
const dragDom = unref(dialogRef as any).$refs.dialogRef
|
||||
dragDom.style.cssText += `;left:0px;top:0px;`
|
||||
}
|
||||
}
|
||||
|
||||
function initDraggable() {
|
||||
nextTick(() => {
|
||||
const dragDom = unref(dialogRef as any).$refs.dialogRef
|
||||
const dialogHeaderEl = dragDom.querySelector('.el-dialog__header') as HTMLElement
|
||||
dragDom.style.cssText += ';top:0px;'
|
||||
dialogHeaderEl.style.cssText += ';cursor:move;user-select:none;'
|
||||
dialogHeaderEl.onmousedown = (e) => {
|
||||
const disX = e.clientX - dialogHeaderEl.offsetLeft
|
||||
const disY = e.clientY - dialogHeaderEl.offsetTop
|
||||
|
||||
const dragDomWidth = dragDom.offsetWidth
|
||||
const dragDomHeight = dragDom.offsetHeight
|
||||
|
||||
const screenWidth = document.body.clientWidth
|
||||
const screenHeight = document.body.clientHeight
|
||||
|
||||
const minDragDomLeft = dragDom.offsetLeft
|
||||
const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth
|
||||
|
||||
const minDragDomTop = dragDom.offsetTop
|
||||
const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomHeight
|
||||
|
||||
const styleLeftStr = getComputedStyle(dragDom).left
|
||||
const styleTopStr = getComputedStyle(dragDom).top
|
||||
if (!styleLeftStr || !styleTopStr) return
|
||||
let styleLeft: number
|
||||
let styleTop: number
|
||||
|
||||
// Format may be "##%" or "##px"
|
||||
if (styleLeftStr.includes('%')) {
|
||||
styleLeft = +document.body.clientWidth * (+styleLeftStr.replace(/%/g, '') / 100)
|
||||
styleTop = +document.body.clientHeight * (+styleTopStr.replace(/%/g, '') / 100)
|
||||
} else {
|
||||
styleLeft = +styleLeftStr.replace(/px/g, '')
|
||||
styleTop = +styleTopStr.replace(/px/g, '')
|
||||
}
|
||||
|
||||
document.onmousemove = (e) => {
|
||||
let left = e.clientX - disX
|
||||
let top = e.clientY - disY
|
||||
|
||||
// Handle edge cases
|
||||
if (-left > minDragDomLeft) {
|
||||
left = -minDragDomLeft
|
||||
} else if (left > maxDragDomLeft) {
|
||||
left = maxDragDomLeft
|
||||
}
|
||||
if (-top > minDragDomTop) {
|
||||
top = -minDragDomTop
|
||||
} else if (top > maxDragDomTop) {
|
||||
top = maxDragDomTop
|
||||
}
|
||||
|
||||
// Move current element
|
||||
dragDom.style.cssText += `;left:${left + styleLeft}px;top:${top + styleTop}px;`
|
||||
}
|
||||
|
||||
document.onmouseup = () => {
|
||||
document.onmousemove = null
|
||||
document.onmouseup = null
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (props.draggable) {
|
||||
initDraggable()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.dialog__icon {
|
||||
position: absolute;
|
||||
top: 22px;
|
||||
right: 45px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
|
||||
&:hover {
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
|
||||
.com-dialog__content {
|
||||
.content__wrap {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
:deep(.el-scrollbar__wrap) {
|
||||
max-height: 600px; // 最大高度
|
||||
overflow-x: hidden; // 隐藏横向滚动栏
|
||||
}
|
||||
}
|
||||
|
||||
.com-dialog__content--fullscreen {
|
||||
:deep(.el-scrollbar__wrap) {
|
||||
height: calc(~'100vh - 46px - 60px'); // 最大高度
|
||||
}
|
||||
}
|
||||
|
||||
.com-dialog__content--footer {
|
||||
:deep(.el-scrollbar__wrap) {
|
||||
max-height: calc(~'100vh - 46px - 60px - 70px'); // 最大高度
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,343 +0,0 @@
|
||||
<template>
|
||||
<div :class="{ search__col: layout === 'right' }">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="layout === 'right' ? 22 : 24">
|
||||
<el-form
|
||||
ref="ruleForm"
|
||||
inline
|
||||
:model="formInline"
|
||||
:rules="rules"
|
||||
:label-width="labelWidth"
|
||||
:label-position="labelPosition"
|
||||
:hide-required-asterisk="hideRequiredAsterisk"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item
|
||||
v-for="(item, $index) in data"
|
||||
:key="$index"
|
||||
:label="item.label"
|
||||
:prop="item.field"
|
||||
:rules="item.rules"
|
||||
>
|
||||
<template v-if="item.itemType === 'switch'">
|
||||
<el-switch
|
||||
v-model="formInline[item.field]"
|
||||
v-bind="{ ...getItemBindValue(item) }"
|
||||
@change="
|
||||
(val) => {
|
||||
changeVal(val, item)
|
||||
}
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-if="item.itemType === 'input'">
|
||||
<el-input
|
||||
v-model="formInline[item.field]"
|
||||
v-bind="{ ...getItemBindValue(item) }"
|
||||
@change="
|
||||
(val) => {
|
||||
changeVal(val, item)
|
||||
}
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-if="item.itemType === 'select'">
|
||||
<el-select
|
||||
v-model="formInline[item.field]"
|
||||
v-bind="{ ...getItemBindValue(item) }"
|
||||
@change="
|
||||
(val) => {
|
||||
changeVal(val, item)
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-option
|
||||
v-for="v in item.options"
|
||||
:key="item.optionValue ? v[item.optionValue] : v.value"
|
||||
:value="item.optionValue ? v[item.optionValue] : v.value"
|
||||
:label="item.optionLabel ? v[item.optionLabel] : v.title"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<template v-if="item.itemType === 'radio'">
|
||||
<el-radio-group
|
||||
v-model="formInline[item.field]"
|
||||
@change="
|
||||
(val) => {
|
||||
changeVal(val, item)
|
||||
}
|
||||
"
|
||||
>
|
||||
<template v-if="item.radioType === 'radio'">
|
||||
<el-radio
|
||||
v-for="v in item.options"
|
||||
:key="item.optionValue ? v[item.optionValue] : v.value"
|
||||
v-bind="{ ...getItemBindValue(item) }"
|
||||
:label="item.optionValue ? v[item.optionValue] : v.value"
|
||||
>
|
||||
{{ item.optionLabel ? v[item.optionLabel] : v.label }}
|
||||
</el-radio>
|
||||
</template>
|
||||
<template v-else-if="item.radioType === 'button'">
|
||||
<el-radio-button
|
||||
v-for="v in item.options"
|
||||
:key="item.optionValue ? v[item.optionValue] : v.value"
|
||||
v-bind="{ ...getItemBindValue(item) }"
|
||||
:label="item.optionValue ? v[item.optionValue] : v.value"
|
||||
>
|
||||
{{ item.optionLabel ? v[item.optionLabel] : v.label }}
|
||||
</el-radio-button>
|
||||
</template>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
|
||||
<!-- element近期会新增treeSelect组件,所以不打算在自己维护一套。等待ing -->
|
||||
<!-- <template v-if="item.itemType === 'treeSelect'">
|
||||
<el-tree-select
|
||||
v-model:value="formInline[item.field]"
|
||||
:size="item.size"
|
||||
:dropdown-style="item.dropdownStyle"
|
||||
:tree-data="item.options"
|
||||
:placeholder="item.placeholder"
|
||||
:tree-checkable="item.treeCheckable"
|
||||
:max-tag-count="item.maxTagCount"
|
||||
:tree-default-expand-all="item.treeDefaultExpandAll"
|
||||
:allow-clear="item.allowClear"
|
||||
style="min-width: 201px;"
|
||||
@change="((val) => {changeVal(val, item)})"
|
||||
>
|
||||
<template #title="{ title }">
|
||||
<span>{{ title }}</span>
|
||||
</template>
|
||||
</el-tree-select>
|
||||
</template> -->
|
||||
|
||||
<template v-if="item.itemType === 'timePicker'">
|
||||
<el-time-picker
|
||||
v-model="formInline[item.field]"
|
||||
v-bind="{ ...getItemBindValue(item) }"
|
||||
@change="
|
||||
(val) => {
|
||||
changeVal(val, item)
|
||||
}
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-if="item.itemType === 'timeSelect'">
|
||||
<el-time-select
|
||||
v-model="formInline[item.field]"
|
||||
v-bind="{ ...getItemBindValue(item) }"
|
||||
@change="
|
||||
(val) => {
|
||||
changeVal(val, item)
|
||||
}
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-if="item.itemType === 'datePicker' || item.itemType === 'dateTimePicker'">
|
||||
<el-date-picker
|
||||
v-model="formInline[item.field]"
|
||||
v-bind="{ ...getItemBindValue(item) }"
|
||||
@change="
|
||||
(val) => {
|
||||
changeVal(val, item)
|
||||
}
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="data.length > 0 && layout === 'classic'">
|
||||
<el-button type="primary" icon="el-icon-search" @click="submitForm"> 查询 </el-button>
|
||||
<el-button v-if="showReset" icon="el-icon-refresh-right" @click="resetForm">
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
<el-col :span="layout === 'right' ? 2 : 24">
|
||||
<div
|
||||
v-if="data.length > 0 && (layout === 'bottom' || layout === 'right')"
|
||||
class="search__bottom"
|
||||
:class="{ 'search__bottom--col': layout === 'right' }"
|
||||
>
|
||||
<div class="search__bottom--button">
|
||||
<el-button type="primary" icon="el-icon-search" @click="submitForm"> 查询 </el-button>
|
||||
</div>
|
||||
<div class="search__bottom--button">
|
||||
<el-button
|
||||
v-if="showReset"
|
||||
:style="{
|
||||
'margin-left': layout !== 'right' ? '15px' : '0',
|
||||
'margin-top': layout === 'right' ? '27px' : '0'
|
||||
}"
|
||||
icon="el-icon-refresh-right"
|
||||
@click="resetForm"
|
||||
>
|
||||
重置
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ComSearch">
|
||||
import { PropType, watch, ref, unref } from 'vue'
|
||||
import { deepClone } from '@/utils'
|
||||
|
||||
const props = defineProps({
|
||||
// 表单域标签的宽度,例如 '50px'。作为 Form 直接子元素的 form-item 会继承该值。支持 auto。
|
||||
labelWidth: {
|
||||
type: String as PropType<string>,
|
||||
default: ''
|
||||
},
|
||||
labelPosition: {
|
||||
type: String as PropType<'right' | 'left' | 'top'>,
|
||||
default: 'right'
|
||||
},
|
||||
// 隐藏所有表单项的必选标记
|
||||
hideRequiredAsterisk: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true
|
||||
},
|
||||
// 表单数据对象
|
||||
data: {
|
||||
type: Object as PropType<{ [key: string]: any }>,
|
||||
default: () => {}
|
||||
},
|
||||
// 表单验证规则
|
||||
rules: {
|
||||
type: Object as PropType<{ [key: number]: any }>,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否显示重置按钮
|
||||
showReset: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true
|
||||
},
|
||||
// 是否显示导出按钮
|
||||
showExport: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false
|
||||
},
|
||||
// 风格
|
||||
layout: {
|
||||
type: String as PropType<'classic' | 'bottom' | 'right'>,
|
||||
default: 'classic'
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['search-submit', 'reset-submit', 'change'])
|
||||
|
||||
const ruleForm = ref<HTMLElement | null>(null)
|
||||
const formInline = ref<IObj>({})
|
||||
watch(
|
||||
() => props.data,
|
||||
(data) => {
|
||||
initForm(data)
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
function getItemBindValue(item: any) {
|
||||
const delArr: string[] = ['label', 'itemType', 'value', 'field']
|
||||
const obj = deepClone(item)
|
||||
for (const key in obj) {
|
||||
if (delArr.indexOf(key) !== -1) {
|
||||
delete obj[key]
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
function initForm(data: any): void {
|
||||
for (const v of data) {
|
||||
formInline.value[v.field] = formInline.value[v.field] || v.value
|
||||
}
|
||||
}
|
||||
|
||||
async function submitForm(): Promise<void> {
|
||||
const form = unref(ruleForm) as any
|
||||
if (!form) return
|
||||
try {
|
||||
form.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
emit('search-submit', unref(formInline))
|
||||
} else {
|
||||
console.log('error submit!!')
|
||||
return false
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
async function resetForm(): Promise<void> {
|
||||
const form = unref(ruleForm) as any
|
||||
if (!form) return
|
||||
await form.resetFields()
|
||||
emit('reset-submit', unref(formInline))
|
||||
}
|
||||
|
||||
function changeVal(val: any, item: any): void {
|
||||
if (item.onChange) {
|
||||
emit('change', {
|
||||
field: item.field,
|
||||
value: val
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ant-form-inline {
|
||||
.ant-form-item {
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.ant-form-item-with-help {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.search__bottom {
|
||||
padding-bottom: 20px;
|
||||
text-align: center;
|
||||
|
||||
.search__bottom--button {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.search__bottom--col {
|
||||
position: relative;
|
||||
padding-bottom: 0;
|
||||
margin-top: 5px;
|
||||
|
||||
.search__bottom--button {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.search__bottom--col::before {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
border-left: 1px solid #d9d9d9;
|
||||
content: '';
|
||||
}
|
||||
</style>
|
||||
@@ -1,37 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { defineComponent, inject, h, PropType } from 'vue'
|
||||
export default defineComponent({
|
||||
name: 'Slot',
|
||||
props: {
|
||||
row: {
|
||||
type: Object as PropType<IObj>,
|
||||
default: () => null
|
||||
},
|
||||
index: {
|
||||
type: Number as PropType<number>,
|
||||
default: null
|
||||
},
|
||||
column: {
|
||||
type: Object as PropType<IObj>,
|
||||
default: () => null
|
||||
},
|
||||
slotName: {
|
||||
type: String as PropType<string>,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
render(props) {
|
||||
const _this: any = inject('tableRoot')
|
||||
return h(
|
||||
'span',
|
||||
_this.slots[props.slotName]({
|
||||
row: props.row,
|
||||
column: props.column,
|
||||
$index: props.index
|
||||
})
|
||||
)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -1,64 +0,0 @@
|
||||
<template>
|
||||
<el-table-column v-bind="{ ...getItemBindValue(child) }" :prop="child.key">
|
||||
<template v-for="item in child.children">
|
||||
<!-- 树型数据 -->
|
||||
<template v-if="item.children && item.children.length">
|
||||
<table-column :key="item[item.field]" :child="item" />
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<el-table-column
|
||||
:key="item[item.field]"
|
||||
v-bind="{ ...getItemBindValue(item) }"
|
||||
:prop="item.field"
|
||||
>
|
||||
<!-- 表头插槽 -->
|
||||
<template v-if="item.slots && item.slots.header" #header="scope">
|
||||
<table-slot
|
||||
v-if="item.slots && item.slots.header"
|
||||
:slot-name="item.slots.header"
|
||||
:column="item"
|
||||
:index="scope.$index"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 表格内容插槽自定义 -->
|
||||
<template v-if="item.slots && item.slots.default" #default="scope">
|
||||
<table-slot
|
||||
:slot-name="item.slots.default"
|
||||
:row="scope.row"
|
||||
:column="item"
|
||||
:index="scope.$index"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="TableColumn">
|
||||
import { PropType } from 'vue'
|
||||
import TableSlot from './Slot.vue'
|
||||
import { deepClone } from '@/utils'
|
||||
|
||||
defineProps({
|
||||
child: {
|
||||
type: Object as PropType<IObj>,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
function getItemBindValue(item: any) {
|
||||
const delArr: string[] = ['children']
|
||||
const obj = deepClone(item)
|
||||
for (const key in obj) {
|
||||
if (delArr.indexOf(key) !== -1) {
|
||||
delete obj[key]
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
||||
@@ -1,159 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-table ref="tableRef" :border="true" v-bind="getBindValue" @header-dragend="headerDragend">
|
||||
<!-- 多选 -->
|
||||
<el-table-column
|
||||
v-if="selection"
|
||||
type="selection"
|
||||
:reserve-selection="reserveSelection"
|
||||
width="40"
|
||||
/>
|
||||
<template v-for="item in columns">
|
||||
<!-- 自定义索引 -->
|
||||
<template v-if="item.type === 'index'">
|
||||
<el-table-column
|
||||
:key="item[item.field]"
|
||||
v-bind="{ ...getItemBindValue(item) }"
|
||||
type="index"
|
||||
:index="item.index"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 树型数据 -->
|
||||
<template v-else-if="item.children && item.children.length">
|
||||
<table-column :key="item[item.field]" :child="item" />
|
||||
</template>
|
||||
|
||||
<template v-else>
|
||||
<el-table-column
|
||||
:key="item[item.field]"
|
||||
v-bind="{ ...getItemBindValue(item) }"
|
||||
:prop="item.field"
|
||||
>
|
||||
<!-- 表头插槽 -->
|
||||
<template v-if="item.slots && item.slots.header" #header="scope">
|
||||
<table-slot
|
||||
v-if="item.slots && item.slots.header"
|
||||
:slot-name="item.slots.header"
|
||||
:column="item"
|
||||
:index="scope.$index"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 表格内容插槽自定义 -->
|
||||
<template v-if="item.slots && item.slots.default" #default="scope">
|
||||
<table-slot
|
||||
v-if="item.slots && item.slots.default"
|
||||
:slot-name="item.slots.default"
|
||||
:row="scope.row"
|
||||
:column="item"
|
||||
:index="scope.$index"
|
||||
/>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
</template>
|
||||
</el-table>
|
||||
|
||||
<div v-if="pagination" class="pagination__wrap">
|
||||
<el-pagination
|
||||
:style="paginationStyle"
|
||||
:page-sizes="[10, 20, 30, 40, 50, 100]"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
v-bind="getPaginationBindValue"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="ComTable">
|
||||
import { PropType, computed, useAttrs, ref, getCurrentInstance, provide } from 'vue'
|
||||
import { deepClone } from '@/utils'
|
||||
import { isObject } from '@/utils/validate'
|
||||
import TableColumn from './components/TableColumn.vue'
|
||||
import TableSlot from './components/Slot.vue'
|
||||
|
||||
const props = defineProps({
|
||||
// 表头
|
||||
columns: {
|
||||
type: Array as PropType<IObj[]>,
|
||||
default: () => []
|
||||
},
|
||||
// 是否多选
|
||||
selection: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false
|
||||
},
|
||||
// 是否展示分页
|
||||
pagination: {
|
||||
type: [Boolean, Object] as PropType<boolean | IObj>,
|
||||
default: false
|
||||
},
|
||||
// 仅对 type=selection 的列有效,类型为 Boolean,为 true 则会在数据更新之后保留之前选中的数据(需指定 row-key)
|
||||
reserveSelection: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const attrs = useAttrs()
|
||||
|
||||
const tableRef = ref<HTMLElement | null>(null)
|
||||
function getTableRef() {
|
||||
return tableRef.value as any
|
||||
}
|
||||
|
||||
const _this = getCurrentInstance()
|
||||
provide('tableRoot', _this)
|
||||
|
||||
const getBindValue = computed((): IObj => {
|
||||
const bindValue = { ...attrs, ...props } as IObj
|
||||
delete bindValue.columns
|
||||
return bindValue
|
||||
})
|
||||
|
||||
function getItemBindValue(item: IObj) {
|
||||
const delArr: string[] = []
|
||||
const obj = deepClone(item)
|
||||
for (const key in obj) {
|
||||
if (delArr.indexOf(key) !== -1) {
|
||||
delete obj[key]
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
const getPaginationBindValue = computed((): IObj => {
|
||||
const PaginationBindValue =
|
||||
props.pagination && isObject(props.pagination) ? { ...props.pagination } : {}
|
||||
return PaginationBindValue
|
||||
})
|
||||
|
||||
const paginationStyle = computed(() => {
|
||||
return {
|
||||
textAlign: (props.pagination && (props.pagination as IObj).position) || 'right'
|
||||
}
|
||||
})
|
||||
|
||||
function headerDragend(newWidth: number, _: number, column: IObj) {
|
||||
// 不懂为啥无法自动计算宽度,只能手动去计算了。。失望ing,到时候看看能不能优化吧。
|
||||
const htmlArr = document.getElementsByClassName(column.id)
|
||||
for (const v of htmlArr as any) {
|
||||
if (v.firstElementChild) {
|
||||
;(v.firstElementChild as any).style.width = newWidth + 'px'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
getTableRef
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.pagination__wrap {
|
||||
padding: 10px;
|
||||
margin-top: 15px;
|
||||
background: #fff;
|
||||
}
|
||||
</style>
|
||||
@@ -1,91 +0,0 @@
|
||||
<template>
|
||||
<div ref="echartRef" :class="className" :style="{ height: height, width: width }"></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="Echart">
|
||||
import { PropType, onMounted, watch, computed, onBeforeUnmount, onActivated, ref, unref } from 'vue'
|
||||
import type { EChartsOption } from 'echarts'
|
||||
import echarts from '@/plugins/echarts'
|
||||
import { debounce } from 'lodash-es'
|
||||
import 'echarts-wordcloud'
|
||||
|
||||
type ThemeType = 'light' | 'dark' | 'default'
|
||||
|
||||
const props = defineProps({
|
||||
options: {
|
||||
type: Object as PropType<EChartsOption>,
|
||||
required: true
|
||||
},
|
||||
className: {
|
||||
type: String as PropType<string>,
|
||||
default: ''
|
||||
},
|
||||
height: {
|
||||
type: String as PropType<string>,
|
||||
default: '500px'
|
||||
},
|
||||
width: {
|
||||
type: String as PropType<string>,
|
||||
default: ''
|
||||
},
|
||||
theme: {
|
||||
type: String as PropType<ThemeType>,
|
||||
default: 'default'
|
||||
}
|
||||
})
|
||||
|
||||
let chartRef: Nullable<echarts.ECharts> = null
|
||||
let sidebarElm: Nullable<Element | any> = null
|
||||
let __resizeHandler: Nullable<any> = null
|
||||
const echartOptions = computed(() => props.options)
|
||||
const echartRef = ref<Nullable<HTMLElement>>(null)
|
||||
|
||||
watch(
|
||||
echartOptions,
|
||||
(options: EChartsOption) => {
|
||||
;(chartRef as echarts.ECharts).setOption(options)
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
function initChart() {
|
||||
chartRef = echarts.init(unref(echartRef) as HTMLElement, props.theme)
|
||||
chartRef.setOption(props.options)
|
||||
}
|
||||
|
||||
function sidebarResizeHandler(e: any): void {
|
||||
if (e.propertyName === 'width') {
|
||||
if (__resizeHandler) {
|
||||
__resizeHandler()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initChart()
|
||||
|
||||
__resizeHandler = debounce(() => {
|
||||
if (chartRef) {
|
||||
chartRef.resize()
|
||||
}
|
||||
}, 100)
|
||||
window.addEventListener('resize', __resizeHandler)
|
||||
sidebarElm = document.getElementsByClassName('sidebar__wrap')[0]
|
||||
sidebarElm && sidebarElm.addEventListener('transitionend', sidebarResizeHandler)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('resize', __resizeHandler)
|
||||
|
||||
sidebarElm && sidebarElm.removeEventListener('transitionend', sidebarResizeHandler)
|
||||
})
|
||||
|
||||
onActivated(() => {
|
||||
// 防止keep-alive之后图表变形
|
||||
if (chartRef) {
|
||||
chartRef.resize()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -1,246 +0,0 @@
|
||||
<template>
|
||||
<div class="wscn-http404-container">
|
||||
<div class="wscn-http404">
|
||||
<div class="pic-404">
|
||||
<img class="pic-404__parent" src="@/assets/img/404.png" alt="404" />
|
||||
<img class="pic-404__child left" src="@/assets/img/404_cloud.png" alt="404" />
|
||||
<img class="pic-404__child mid" src="@/assets/img/404_cloud.png" alt="404" />
|
||||
<img class="pic-404__child right" src="@/assets/img/404_cloud.png" alt="404" />
|
||||
</div>
|
||||
<div class="bullshit">
|
||||
<div class="bullshit__oops"> OOPS! </div>
|
||||
<div class="bullshit__headline">
|
||||
{{ message }}
|
||||
</div>
|
||||
<div class="bullshit__info"> 请检查您输入的网址是否正确,请点击以下按钮返回主页 </div>
|
||||
<router-link to="/">
|
||||
<a href="" class="bullshit__return-home">返回首页</a>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="404">
|
||||
import { ref } from 'vue'
|
||||
const message = ref<string>('网管说这个页面你不能进......')
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.wscn-http404-container {
|
||||
position: absolute;
|
||||
top: 40%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.wscn-http404 {
|
||||
position: relative;
|
||||
width: 1200px;
|
||||
padding: 0 50px;
|
||||
overflow: hidden;
|
||||
|
||||
.pic-404 {
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 600px;
|
||||
overflow: hidden;
|
||||
|
||||
&__parent {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__child {
|
||||
@keyframes cloudLeft {
|
||||
0% {
|
||||
top: 17px;
|
||||
left: 220px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
20% {
|
||||
top: 33px;
|
||||
left: 188px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
80% {
|
||||
top: 81px;
|
||||
left: 92px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
top: 97px;
|
||||
left: 60px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes cloudMid {
|
||||
0% {
|
||||
top: 10px;
|
||||
left: 420px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
20% {
|
||||
top: 40px;
|
||||
left: 360px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
70% {
|
||||
top: 130px;
|
||||
left: 180px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
top: 160px;
|
||||
left: 120px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@keyframes cloudRight {
|
||||
0% {
|
||||
top: 100px;
|
||||
left: 500px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
20% {
|
||||
top: 120px;
|
||||
left: 460px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
80% {
|
||||
top: 180px;
|
||||
left: 340px;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
top: 200px;
|
||||
left: 300px;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
position: absolute;
|
||||
|
||||
&.left {
|
||||
top: 17px;
|
||||
left: 220px;
|
||||
width: 80px;
|
||||
opacity: 0;
|
||||
animation-delay: 1s;
|
||||
animation-duration: 2s;
|
||||
animation-name: cloudLeft;
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&.mid {
|
||||
top: 10px;
|
||||
left: 420px;
|
||||
width: 46px;
|
||||
opacity: 0;
|
||||
animation-delay: 1.2s;
|
||||
animation-duration: 2s;
|
||||
animation-name: cloudMid;
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&.right {
|
||||
top: 100px;
|
||||
left: 500px;
|
||||
width: 62px;
|
||||
opacity: 0;
|
||||
animation-delay: 1s;
|
||||
animation-duration: 2s;
|
||||
animation-name: cloudRight;
|
||||
animation-timing-function: linear;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bullshit {
|
||||
@keyframes slideUp {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(60px);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
position: relative;
|
||||
float: left;
|
||||
width: 300px;
|
||||
padding: 30px 0;
|
||||
overflow: hidden;
|
||||
|
||||
&__oops {
|
||||
margin-bottom: 20px;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
line-height: 40px;
|
||||
color: #1482f0;
|
||||
opacity: 0;
|
||||
animation-duration: 0.5s;
|
||||
animation-name: slideUp;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&__headline {
|
||||
margin-bottom: 10px;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
line-height: 24px;
|
||||
color: #222;
|
||||
opacity: 0;
|
||||
animation-delay: 0.1s;
|
||||
animation-duration: 0.5s;
|
||||
animation-name: slideUp;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&__info {
|
||||
margin-bottom: 30px;
|
||||
font-size: 13px;
|
||||
line-height: 21px;
|
||||
color: grey;
|
||||
opacity: 0;
|
||||
animation-delay: 0.2s;
|
||||
animation-duration: 0.5s;
|
||||
animation-name: slideUp;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
&__return-home {
|
||||
display: block;
|
||||
float: left;
|
||||
width: 110px;
|
||||
height: 36px;
|
||||
font-size: 14px;
|
||||
line-height: 36px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
background: #1482f0;
|
||||
border-radius: 100px;
|
||||
opacity: 0;
|
||||
animation-delay: 0.3s;
|
||||
animation-duration: 0.5s;
|
||||
animation-name: slideUp;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
52
src/components/HelloWorld.vue
Normal file
52
src/components/HelloWorld.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps<{ msg: string }>()
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
|
||||
<p>
|
||||
Recommended IDE setup:
|
||||
<a href="https://code.visualstudio.com/" target="_blank">VSCode</a>
|
||||
+
|
||||
<a href="https://github.com/johnsoncodehk/volar" target="_blank">Volar</a>
|
||||
</p>
|
||||
|
||||
<p>See <code>README.md</code> for more information.</p>
|
||||
|
||||
<p>
|
||||
<a href="https://vitejs.dev/guide/features.html" target="_blank">
|
||||
Vite Docs
|
||||
</a>
|
||||
|
|
||||
<a href="https://v3.vuejs.org/" target="_blank">Vue 3 Docs</a>
|
||||
</p>
|
||||
|
||||
<button type="button" @click="count++">count is: {{ count }}</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test hot module replacement.
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
a {
|
||||
color: #42b983;
|
||||
}
|
||||
|
||||
label {
|
||||
margin: 0 0.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
code {
|
||||
background-color: #eee;
|
||||
padding: 2px 4px;
|
||||
border-radius: 4px;
|
||||
color: #304455;
|
||||
}
|
||||
</style>
|
||||
@@ -1,23 +0,0 @@
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
let messageInstance: Nullable<any> = null
|
||||
|
||||
const resetMessage = (options: any) => {
|
||||
if (messageInstance) {
|
||||
messageInstance.close()
|
||||
}
|
||||
messageInstance = ElMessage(options)
|
||||
}
|
||||
;['error', 'success', 'info', 'warning'].forEach((type: string) => {
|
||||
resetMessage[type] = (options: any) => {
|
||||
if (typeof options === 'string') {
|
||||
options = {
|
||||
message: options
|
||||
}
|
||||
}
|
||||
options.type = type
|
||||
return resetMessage(options)
|
||||
}
|
||||
})
|
||||
|
||||
export const Message = resetMessage as any
|
||||
@@ -1,16 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<router-view>
|
||||
<template #default="{ Component, route }">
|
||||
<keep-alive :include="getCaches">
|
||||
<component :is="Component" :key="route.fullPath" />
|
||||
</keep-alive>
|
||||
</template>
|
||||
</router-view>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts" name="ParentView">
|
||||
import { useCache } from './useCache'
|
||||
|
||||
const { getCaches } = useCache(false)
|
||||
</script>
|
||||
@@ -1,55 +0,0 @@
|
||||
import { computed, ref, unref, ComponentInternalInstance, getCurrentInstance } from 'vue'
|
||||
|
||||
import { useTagsViewStoreWithOut, PAGE_LAYOUT_KEY } from '@/store/modules/tags-view'
|
||||
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
function tryTsxEmit<T extends any = ComponentInternalInstance>(
|
||||
fn: (_instance: T) => Promise<void> | void
|
||||
) {
|
||||
const instance = getCurrentInstance() as any
|
||||
instance && fn.call(null, instance)
|
||||
}
|
||||
|
||||
const ParentLayoutName = 'ParentLayout'
|
||||
export function useCache(isPage: boolean) {
|
||||
const tagsViewStore = useTagsViewStoreWithOut()
|
||||
const name = ref('')
|
||||
const { currentRoute } = useRouter()
|
||||
|
||||
tryTsxEmit((instance) => {
|
||||
const routeName = instance.type.name
|
||||
if (routeName && ![ParentLayoutName].includes(routeName)) {
|
||||
name.value = routeName
|
||||
} else {
|
||||
const matched = currentRoute.value.matched
|
||||
const len = matched.length
|
||||
if (len < 2) return
|
||||
name.value = matched[len - 2].name as string
|
||||
}
|
||||
})
|
||||
|
||||
const getCaches = computed((): string[] => {
|
||||
const cached = tagsViewStore.getCachedViews
|
||||
|
||||
if (isPage) {
|
||||
// page Layout
|
||||
// not parent layout
|
||||
return (cached as any).get(PAGE_LAYOUT_KEY) || []
|
||||
}
|
||||
const cacheSet = new Set<string>()
|
||||
cacheSet.add(unref(name))
|
||||
|
||||
const list = (cached as any).get(unref(name))
|
||||
|
||||
if (!list) {
|
||||
return Array.from(cacheSet)
|
||||
}
|
||||
;(list as string[]).forEach((item: string) => {
|
||||
cacheSet.add(item)
|
||||
})
|
||||
|
||||
return Array.from(cacheSet)
|
||||
})
|
||||
return { getCaches }
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { unref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
const { currentRoute, replace } = useRouter()
|
||||
const { params, query } = unref(currentRoute)
|
||||
const { path } = params
|
||||
const _path = Array.isArray(path) ? path.join('/') : path
|
||||
replace({
|
||||
path: '/' + _path,
|
||||
query
|
||||
})
|
||||
</script>
|
||||
@@ -1,41 +0,0 @@
|
||||
<template>
|
||||
<svg :class="svgClass" aria-hidden="true" v-on="$attrs">
|
||||
<use :xlink:href="iconName" />
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="SvgIcon">
|
||||
import { PropType, computed } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
// svg文件名
|
||||
iconClass: {
|
||||
type: String as PropType<string>,
|
||||
required: true
|
||||
},
|
||||
// 自定义类名
|
||||
className: {
|
||||
type: String as PropType<string>,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
|
||||
const iconName = computed(() => `#icon-${props.iconClass}`)
|
||||
const svgClass = computed(() => {
|
||||
if (props.className) {
|
||||
return `svg-icon ${props.className}`
|
||||
} else {
|
||||
return 'svg-icon'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.svg-icon {
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
overflow: hidden;
|
||||
vertical-align: -0.15em;
|
||||
fill: currentColor;
|
||||
}
|
||||
</style>
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { App } from 'vue'
|
||||
import SvgIcon from './SvgIcon/index.vue' // svg组件
|
||||
import ComSearch from './ComSearch/index.vue' // search组件
|
||||
import ComDialog from './ComDialog/index.vue' // dialog组件
|
||||
import ComDetail from './ComDetail/index.vue' // detail组件
|
||||
import ComTable from './ComTable/index.vue' // table组件
|
||||
|
||||
export function setupGlobCom(app: App<Element>): void {
|
||||
app.component('SvgIcon', SvgIcon)
|
||||
app.component('ComSearch', ComSearch)
|
||||
app.component('ComDialog', ComDialog)
|
||||
app.component('ComDetail', ComDetail)
|
||||
app.component('ComTable', ComTable)
|
||||
}
|
||||
Reference in New Issue
Block a user