feat: 🎸 layout三种布局重构完成

This commit is contained in:
chenkl 2020-12-21 18:21:27 +08:00
parent bd24b92acb
commit 429e42809c
90 changed files with 3945 additions and 4699 deletions
package.json
src
components
directives/clipboard
libs
pages/index
axios-config
config
layout
main.tspermission.ts
router
store/modules
views
components-demo
dashboard
directives-demo/clipboard
hooks-demo
useScrollTo
useWatermark
icons
login
table-demo
basic-table
basic-usage
border-table
custom-header
custom-menu
edit-cell
edit-row
expand-row
fixed-column-header
fixed-column
fixed-header
fluid-height
multi-header
multiple-choice
screen-table
single-choice
sort-table
state-table
stripe-table
table-border
table-ellipsis
table-expanded
table-load
table-merge
table-tree
test
total-table
tree-and-load
styles
yarn.lock

View File

@ -1,5 +1,5 @@
{
"name": "vue-element-admin-webpack",
"name": "vue-element-plus-admin-webpack",
"version": "0.1.0",
"private": true,
"scripts": {
@ -19,7 +19,7 @@
"clipboard": "^2.0.6",
"core-js": "^3.6.5",
"echarts": "^4.9.0",
"element-plus": "^1.0.1-beta.8",
"element-plus": "1.0.1-beta.10",
"highlight.js": "^10.4.0",
"lodash-es": "^4.17.15",
"mockjs": "^1.1.0",

View File

@ -1,5 +1,5 @@
import { PropType } from 'vue'
import { message } from 'ant-design-vue'
import { ElMessage } from 'element-plus'
import { oneOf } from '@/utils'
import { Config } from './types'
@ -18,19 +18,19 @@ export const editorProps = {
customAlert: (s: string, t: string) => {
switch (t) {
case 'success':
message.success(s)
ElMessage.success(s)
break
case 'info':
message.info(s)
ElMessage.info(s)
break
case 'warning':
message.warning(s)
ElMessage.warning(s)
break
case 'error':
message.error(s)
ElMessage.error(s)
break
default:
message.info(s)
ElMessage.info(s)
break
}
},

View File

@ -1,243 +0,0 @@
<template>
<div ref="imageRef" class="image">
<slot v-if="loading" name="placeholder">
<div class="image__placeholder" />
</slot>
<slot v-else-if="error" name="error">
<div class="image__error">加载失败</div>
</slot>
<img
v-else
v-bind="$attrs"
:src="src"
:style="imageStyle"
:class="{ 'image__inner--center': alignCenter }"
class="image__inner"
>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, ref, computed, watch, onMounted, onBeforeUnmount, getCurrentInstance, unref } from 'vue'
import { on, off, getScrollContainer, isInContainer } from '@/utils/dom-utils'
import { isString, isElement } from '@/utils/is'
import throttle from 'lodash-es/throttle'
const isSupportObjectFit = () => document.documentElement.style.objectFit !== undefined
const ObjectFit = {
NONE: 'none',
CONTAIN: 'contain',
COVER: 'cover',
FILL: 'fill',
SCALE_DOWN: 'scale-down'
}
export default defineComponent({
name: 'Image',
// inheritAttrs: false,
props: {
src: {
type: String as PropType<string>,
default: ''
},
fit: {
type: String as PropType<string>,
default: ''
},
lazy: {
type: Boolean as PropType<boolean>,
default: false
},
scrollContainer: {
type: Object as PropType<any>,
default: null
}
},
emits: ['error'],
setup(props, { emit }) {
const { ctx } = getCurrentInstance() as any
const imageRef = ref<HTMLElement | null>(null)
const loading = ref<boolean>(true)
const error = ref<boolean>(false)
const show = ref<boolean>(!props.lazy)
const imageWidth = ref<number>(0)
const imageHeight = ref<number>(0)
const imageStyle = computed((): any => {
const { fit } = props
// if (!isServer && fit) {
if (fit) {
return isSupportObjectFit()
? { 'object-fit': fit }
: getImageStyle(fit)
}
return {}
})
const alignCenter = computed((): boolean => {
const { fit } = props
// return !isServer && !isSupportObjectFit() && fit !== ObjectFit.FILL
return !isSupportObjectFit() && fit !== ObjectFit.FILL
})
let _scrollContainer: any = null
let _lazyLoadHandler: any = null
watch(
() => show.value,
(show: boolean) => {
show && loadImage()
}
)
watch(
() => props.src,
() => {
show.value && loadImage()
}
)
onMounted(() => {
if (props.lazy) {
addLazyLoadListener()
} else {
loadImage()
}
})
onBeforeUnmount(() => {
props.lazy && removeLazyLoadListener()
})
function loadImage(): void {
// reset status
loading.value = true
error.value = false
const img = new Image()
img.onload = (e: any) => handleLoad(e, img)
img.onerror = (e: any) => handleError(e)
// bind html attrs
// so it can behave consistently
Object.keys(ctx.$attrs)
.forEach((key) => {
const value = ctx.$attrs[key]
img.setAttribute(key, value)
})
img.src = props.src
}
function handleLoad(e: any, img: any): void {
imageWidth.value = img.width
imageHeight.value = img.height
loading.value = false
}
function handleError(e: any): void {
loading.value = false
error.value = true
emit('error', e)
}
function handleLazyLoad(): void {
const imageRefWrap = unref(imageRef) as any
if (isInContainer(imageRefWrap, _scrollContainer)) {
show.value = true
removeLazyLoadListener()
}
}
function addLazyLoadListener(): void {
// if (isServer) return
const { scrollContainer } = props
let __scrollContainer = null
if (isElement(scrollContainer)) {
__scrollContainer = scrollContainer
} else if (isString(scrollContainer)) {
__scrollContainer = document.querySelector(scrollContainer as any)
} else {
const imageRefWrap = unref(imageRef) as any
__scrollContainer = getScrollContainer(imageRefWrap)
}
if (__scrollContainer) {
_scrollContainer = __scrollContainer
_lazyLoadHandler = throttle(handleLazyLoad, 200)
on(__scrollContainer, 'scroll', _lazyLoadHandler)
handleLazyLoad()
}
}
function removeLazyLoadListener(): void {
// if (isServer || !_scrollContainer || !_lazyLoadHandler) return
if (!_scrollContainer || !_lazyLoadHandler) return
off(_scrollContainer, 'scroll', _lazyLoadHandler)
_scrollContainer = null
_lazyLoadHandler = null
}
/**
* simulate object-fit behavior to compatible with IE11 and other browsers which not support object-fit
*/
function getImageStyle(fit: string): object {
const imageRefWrap = unref(imageRef) as any
const {
clientWidth: containerWidth,
clientHeight: containerHeight
} = imageRefWrap
if (!imageWidth.value || !imageHeight.value || !containerWidth || !containerHeight) return {}
const vertical: boolean = imageWidth.value / imageHeight.value < 1
if (fit === ObjectFit.SCALE_DOWN) {
const isSmaller: boolean = imageWidth.value < containerWidth && imageHeight.value < containerHeight
fit = isSmaller ? ObjectFit.NONE : ObjectFit.CONTAIN
}
switch (fit) {
case ObjectFit.NONE:
return { width: 'auto', height: 'auto' }
case ObjectFit.CONTAIN:
return vertical ? { width: 'auto' } : { height: 'auto' }
case ObjectFit.COVER:
return vertical ? { height: 'auto' } : { width: 'auto' }
default:
return {}
}
}
return {
imageRef,
loading, error, show,
imageStyle, alignCenter
}
}
})
</script>
<style lang="less" scoped>
.image {
position: relative;
display: inline-block;
overflow: hidden;
.image__placeholder {
background: #f5f7fa;
}
.image__error {
display: flex;
justify-content: center;
align-items: center;
font-size: 14px;
color: #c0c4cc;
vertical-align: middle;
height: 100%;
height: 100%;
background: #f5f7fa;
}
}
</style>

View File

@ -19,7 +19,7 @@ export default defineComponent({
},
setup(props) {
const show = ref<boolean>(true)
const title = computed(() => appStore.title)
const title = computed(() => appStore.logoTitle)
const layout = computed(() => appStore.layout)
watch(
() => props.collapsed,

View File

@ -10,7 +10,7 @@
<div class="image-viewer__mask" />
<!-- CLOSE -->
<span class="image-viewer__btn image-viewer__close" @click="hide">
<CloseCircleOutlined class="iconfont" />
<i class="el-icon-circle-close iconfont" />
</span>
<!-- ARROW -->
<template v-if="!isSingle">
@ -19,14 +19,14 @@
:class="{ 'is-disabled': !infinite && isFirst }"
@click="prev"
>
<LeftOutlined class="iconfont" />
<i class="el-icon-arrow-left iconfont" />
</span>
<span
class="image-viewer__btn image-viewer__next"
:class="{ 'is-disabled': !infinite && isLast }"
@click="next"
>
<RightOutlined class="iconfont" />
<i class="el-icon-arrow-right iconfont" />
</span>
</template>
<!-- ACTIONS -->
@ -63,15 +63,11 @@ import { isFirefox } from '@/utils/is'
import { on, off } from '@/utils/dom-utils'
import throttle from 'lodash-es/throttle'
import SvgIcon from '_c/SvgIcon/index.vue'
import { CloseCircleOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons-vue'
const mousewheelEventName = isFirefox() ? 'DOMMouseScroll' : 'mousewheel'
export default defineComponent({
name: 'Preview',
components: {
SvgIcon,
CloseCircleOutlined,
LeftOutlined,
RightOutlined
SvgIcon
},
props: previewProps,
setup(props) {

View File

@ -1,128 +0,0 @@
<template>
<div
ref="elRef"
:class="['scrollbar__bar', 'is-' + bar.key]"
@mousedown="clickTrackHandler"
>
<div
ref="thumbRef"
class="scrollbar__thumb"
:style="renderThumbStyle({ size, move, bar })"
@mousedown="clickThumbHandler"
/>
</div>
</template>
<script lang="ts">
import type { PropType } from 'vue'
import { defineComponent, computed, unref, inject, Ref, reactive, ref, onBeforeUnmount } from 'vue'
import { renderThumbStyle, BAR_MAP } from './util'
import { on, off } from '@/utils/dom-utils'
export default defineComponent({
name: 'Bar',
props: {
vertical: {
type: Boolean as PropType<boolean>,
default: false
},
size: {
type: String as PropType<string>,
default: ''
},
move: {
type: Number as PropType<number>,
default: 0
}
},
setup(props) {
const thumbRef = ref<HTMLElement | null>(null)
const elRef = ref<HTMLElement | null>(null)
const commonState = reactive<any>({})
const getBarRef = computed(() => {
return BAR_MAP[props.vertical ? 'vertical' : 'horizontal']
})
const bar = unref(getBarRef)
const parentElRef = inject('scroll-bar-wrap') as Ref<HTMLElement>
function clickThumbHandler(e: any) {
const { ctrlKey, button, currentTarget } = e
// prevent click event of right button
if (ctrlKey || button === 2 || !currentTarget) {
return
}
startDrag(e)
const bar = unref(getBarRef)
commonState[bar.axis] =
currentTarget[bar.offset] -
(e[bar.client as keyof typeof e] - currentTarget.getBoundingClientRect()[bar.direction])
}
function clickTrackHandler(e: any) {
const bar = unref(getBarRef)
const offset = Math.abs(e.target.getBoundingClientRect()[bar.direction] - e[bar.client])
const thumbEl = unref(thumbRef) as any
const parentEl = unref(parentElRef) as any
const el = unref(elRef) as any
if (!thumbEl || !el || !parentEl) return
const thumbHalf = thumbEl[bar.offset] / 2
const thumbPositionPercentage = ((offset - thumbHalf) * 100) / el[bar.offset]
parentEl[bar.scroll] = (thumbPositionPercentage * parentEl[bar.scrollSize]) / 100
}
function startDrag(e: Event) {
e.stopImmediatePropagation()
commonState.cursorDown = true
on(document, 'mousemove', mouseMoveDocumentHandler)
on(document, 'mouseup', mouseUpDocumentHandler)
document.onselectstart = () => false
}
function mouseMoveDocumentHandler(e: any) {
if (commonState.cursorDown === false) return
const bar = unref(getBarRef)
const prevPage = commonState[bar.axis]
const el = unref(elRef) as any
const parentEl = unref(parentElRef) as any
const thumbEl = unref(thumbRef) as any
if (!prevPage || !el || !thumbEl || !parentEl) return
const rect = el.getBoundingClientRect() as any
const offset = (rect[bar.direction] - e[bar.client]) * -1
const thumbClickPosition = thumbEl[bar.offset] - prevPage
const thumbPositionPercentage = ((offset - thumbClickPosition) * 100) / el[bar.offset]
parentEl[bar.scroll] = (thumbPositionPercentage * parentEl[bar.scrollSize]) / 100
}
function mouseUpDocumentHandler() {
const bar = unref(getBarRef)
commonState.cursorDown = false
commonState[bar.axis] = 0
off(document, 'mousemove', mouseMoveDocumentHandler)
document.onselectstart = null
}
onBeforeUnmount(() => {
off(document, 'mouseup', mouseUpDocumentHandler)
})
return {
thumbRef,
elRef,
bar,
clickThumbHandler,
clickTrackHandler,
renderThumbStyle
}
}
})
</script>
<style lang="less" scoped>
.scrollbar__bar.is-vertical>div {
width: 100%;
}
.scrollbar__bar.is-horizontal>div {
height: 100%;
}
</style>

View File

@ -1,254 +0,0 @@
<template>
<div class="scrollbar">
<template v-if="!native">
<div
ref="wrapElRef"
:style="style"
:class="[wrapClass, 'scrollbar__wrap', gutter ? '' : 'scrollbar__wrap--hidden-default']"
@scroll="handleScroll"
>
<div
ref="resizeRef"
:class="['scrollbar__view', viewClass]"
:style="viewStyle"
>
<slot />
</div>
</div>
<bar v-if="showX" :move="state.moveX" :size="state.sizeWidth" />
<bar v-if="showY" vertical :move="state.moveY" :size="state.sizeHeight" />
</template>
<template v-else>
<div
ref="wrap"
:class="[wrapClass, 'scrollbar__wrap']"
:style="style"
>
<div
ref="resizeRef"
:class="['scrollbar__view', viewClass]"
:style="viewStyle"
>
<slot />
</div>
</div>
</template>
</div>
</template>
<script lang="ts">
import {
defineComponent,
PropType,
unref,
reactive,
ref,
toRef,
provide,
onMounted,
nextTick,
onBeforeUnmount,
getCurrentInstance
} from 'vue'
import { addResizeListener, removeResizeListener } from '@/utils/event/resize-event'
import scrollbarWidth from '@/utils/scrollbar-width'
import { isString } from '@/utils/is'
import { toObject } from './util'
import Bar from './Bar.vue'
export default defineComponent({
name: 'Scrollbar',
components: {
Bar
},
props: {
native: {
type: Boolean as PropType<boolean>,
default: false
},
wrapStyle: {
type: Object as PropType<any>,
default: () => null
},
wrapClass: {
type: String as PropType<string>, required: false,
default: ''
},
viewClass: {
type: String as PropType<string>,
default: ''
},
viewStyle: {
type: Object as PropType<any>,
default: () => {}
},
noresize: {
type: Boolean as PropType<boolean>,
default: false
},
showX: {
type: Boolean as PropType<boolean>,
default: true
},
showY: {
type: Boolean as PropType<boolean>,
default: true
}
// tag: {
// type: String as PropType<string>,
// default: 'div'
// }
},
setup(props) {
const resizeRef = ref<HTMLElement | null>(null)
const wrapElRef = ref<HTMLElement | null>(null)
provide('scroll-bar-wrap', wrapElRef)
const state = reactive({
sizeWidth: '0',
sizeHeight: '0',
moveX: 0,
moveY: 0
})
let style: any = toRef(props, 'wrapStyle')
const gutter = scrollbarWidth()
if (gutter) {
const gutterWith = `-${gutter}px`
const gutterStyle = `margin-bottom: ${gutterWith}; margin-right: ${gutterWith};`
if (Array.isArray(props.wrapStyle)) {
style = toObject(props.wrapStyle)
style.value.marginRight = style.value.marginBottom = gutterWith
} else if (isString(props.wrapStyle)) {
style.value += gutterStyle
} else {
style = gutterStyle
}
}
function handleScroll() {
const warpEl = unref(wrapElRef)
if (!warpEl) return
const { scrollTop, scrollLeft, clientHeight, clientWidth } = warpEl
state.moveY = (scrollTop * 100) / clientHeight
state.moveX = (scrollLeft * 100) / clientWidth
}
function update() {
const warpEl = unref(wrapElRef)
if (!warpEl) return
const { scrollHeight, scrollWidth, clientHeight, clientWidth } = warpEl
const heightPercentage = (clientHeight * 100) / scrollHeight
const widthPercentage = (clientWidth * 100) / scrollWidth
state.sizeHeight = heightPercentage < 100 ? heightPercentage + '%' : ''
state.sizeWidth = widthPercentage < 100 ? widthPercentage + '%' : ''
}
onMounted(() => {
const instance = getCurrentInstance() as any
if (instance) {
instance.wrap = unref(wrapElRef)
}
const { native, noresize } = props
const resizeEl = unref(resizeRef)
const warpEl = unref(wrapElRef)
if (native || !resizeEl || !warpEl) return
nextTick(update)
if (!noresize) {
addResizeListener(resizeEl, update)
addResizeListener(warpEl, update)
}
})
onBeforeUnmount(() => {
const { native, noresize } = props
const resizeEl = unref(resizeRef)
const warpEl = unref(wrapElRef)
if (native || !resizeEl || !warpEl) return
if (!noresize) {
removeResizeListener(resizeEl, update)
removeResizeListener(warpEl, update)
}
})
return {
resizeRef, wrapElRef,
state, gutter, style,
handleScroll
}
}
})
</script>
<style lang="less" scoped>
.scrollbar {
position: relative;
overflow: hidden;
height: 100%;
&__wrap {
height: 100%;
overflow: scroll;
overflow-x: hidden;
&--hidden-default {
scrollbar-width: none;
&::-webkit-scrollbar {
width: 0;
height: 0;
}
}
}
@{deep}(&__thumb) {
position: relative;
display: block;
width: 0;
height: 0;
cursor: pointer;
background-color: rgba(144, 147, 153, 0.3);
border-radius: inherit;
transition: 0.3s background-color;
&:hover {
background-color: rgba(144, 147, 153, 0.5);
}
}
&__bar {
position: absolute;
right: 2px;
bottom: 2px;
z-index: 1;
border-radius: 4px;
opacity: 0;
-webkit-transition: opacity 120ms ease-out;
transition: opacity 120ms ease-out;
&.is-vertical {
top: 2px;
width: 6px;
& > div {
width: 100%;
}
}
&.is-horizontal {
left: 2px;
height: 6px;
& > div {
height: 100%;
}
}
}
}
.scrollbar:active > .scrollbar__bar,
.scrollbar:focus > .scrollbar__bar,
.scrollbar:hover > .scrollbar__bar {
opacity: 1;
transition: opacity 280ms ease-out;
}
</style>

View File

@ -1,14 +0,0 @@
export interface BarMapItem {
offset: string
scroll: string
scrollSize: string
size: string
key: string
axis: string
client: string
direction: string
}
export interface BarMap {
vertical: BarMapItem
horizontal: BarMapItem
}

View File

@ -1,49 +0,0 @@
import type { BarMap } from './types'
export const BAR_MAP: BarMap = {
vertical: {
offset: 'offsetHeight',
scroll: 'scrollTop',
scrollSize: 'scrollHeight',
size: 'height',
key: 'vertical',
axis: 'Y',
client: 'clientY',
direction: 'top'
},
horizontal: {
offset: 'offsetWidth',
scroll: 'scrollLeft',
scrollSize: 'scrollWidth',
size: 'width',
key: 'horizontal',
axis: 'X',
client: 'clientX',
direction: 'left'
}
}
export function renderThumbStyle({ move, size, bar }: any) {
const style = {} as any
const translate = `translate${bar.axis}(${move}%)`
style[bar.size] = size
style.transform = translate
style.msTransform = translate
style.webkitTransform = translate
return style
}
function extend<T, K>(to: T, _from: K): T & K {
return Object.assign(to, _from)
}
export function toObject<T>(arr: Array<T>): Record<string, T> {
const res = {}
for (let i = 0; i < arr.length; i++) {
if (arr[i]) {
extend(res, arr[i])
}
}
return res
}

View File

@ -1,96 +1,86 @@
<template>
<div :class="{ search__col: layout === 'right' }">
<a-row :gutter="20">
<a-col :span="layout === 'right' ? 22 : 24">
<a-form
<el-row :gutter="20">
<el-col :span="layout === 'right' ? 22 : 24">
<el-form
ref="ruleForm"
layout="inline"
inline
:model="formInline"
:rules="rules"
:label-col="labelCol"
:wrapper-col="wrapperCol"
:label-align="labelAlign"
:hide-required-mark="hideRequiredMark"
:label-width="labelWidth"
:label-position="labelPosition"
:hide-required-asterisk="hideRequiredAsterisk"
@submit.prevent
>
<a-form-item
<el-form-item
v-for="(item, $index) in data"
:key="$index"
:label="item.label"
:name="item.field"
:prop="item.field"
:rules="item.rules"
>
<template v-if="item.type === 'switch'">
<a-switch
v-model:checked="formInline[item.field]"
:size="item.size"
:checked-children="item.checkedChildren"
:un-checked-children="item.unCheckedChildren"
<template v-if="item.itemType === 'switch'">
<el-switch
v-model="formInline[item.field]"
v-bind="{...bindValue(item)}"
@change="((val) => {changeVal(val, item)})"
/>
</template>
<template v-if="item.type === 'input'">
<a-input
v-model:value="formInline[item.field]"
:size="item.size"
:maxlength="item.maxlength"
:placeholder="item.placeholder"
:addon-before="item.addonBefore"
:addon-after="item.addonAfter"
:allow-clear="item.allowClear"
<template v-if="item.itemType === 'input'">
<el-input
v-model="formInline[item.field]"
v-bind="{...bindValue(item)}"
@change="((val) => {changeVal(val, item)})"
/>
</template>
<template v-if="item.type === 'select'">
<a-select
v-model:value="formInline[item.field]"
:size="item.size"
:placeholder="item.placeholder"
:allow-clear="item.allowClear"
style="min-width: 201px;"
<template v-if="item.itemType === 'select'">
<el-select
v-model="formInline[item.field]"
v-bind="{...bindValue(item)}"
@change="((val) => {changeVal(val, item)})"
>
<a-select-option
<el-option
v-for="v in item.options"
:key="item.optionValue ? v[item.optionValue] : v.value"
:value="item.optionValue ? v[item.optionValue] : v.value"
>
{{ item.optionLabel ? v[item.optionLabel] : v.title }}
</a-select-option>
</a-select>
:label="item.optionLabel ? v[item.optionLabel] : v.title"
/>
</el-select>
</template>
<template v-if="item.type === 'radio'">
<a-radio-group
v-model:value="formInline[item.field]"
:size="item.size"
<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'">
<a-radio
<el-radio
v-for="v in item.options"
:key="item.optionValue ? v[item.optionValue] : v.value"
:value="item.optionValue ? v[item.optionValue] : v.value"
v-bind="{...bindValue(item)}"
:label="item.optionValue ? v[item.optionValue] : v.value"
>
{{ item.optionLabel ? v[item.optionLabel] : v.label }}
</a-radio>
</el-radio>
</template>
<template v-else-if="item.radioType === 'button'">
<a-radio-button
<el-radio-button
v-for="v in item.options"
:key="item.optionValue ? v[item.optionValue] : v.value"
:value="item.optionValue ? v[item.optionValue] : v.value"
v-bind="{...bindValue(item)}"
:label="item.optionValue ? v[item.optionValue] : v.value"
>
{{ item.optionLabel ? v[item.optionLabel] : v.label }}
</a-radio-button>
</el-radio-button>
</template>
</a-radio-group>
</el-radio-group>
</template>
<template v-if="item.type === 'treeSelect'">
<a-tree-select
<!-- 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"
@ -106,161 +96,102 @@
<template #title="{ title }">
<span>{{ title }}</span>
</template>
</a-tree-select>
</template>
</el-tree-select>
</template> -->
<template v-if="item.type === 'datePicker'">
<a-date-picker
v-model:value="formInline[item.field]"
:format="item.format"
:mode="item.mode"
:value-format="item.valueFormat"
:placeholder="item.placeholder"
:size="item.size"
:allow-clear="item.allowClear"
:disabled-date="item.disabledDate"
:disabled-time="item.disabledDateTime"
:show-time="item.showTime"
<template v-if="item.itemType === 'timePicker'">
<el-time-picker
v-model="formInline[item.field]"
v-bind="{...bindValue(item)}"
@change="((val) => {changeVal(val, item)})"
/>
</template>
<template v-if="item.type === 'monthPicker'">
<a-month-picker
v-model:value="formInline[item.field]"
:format="item.format"
:mode="item.mode"
:value-format="item.valueFormat"
:placeholder="item.placeholder"
:size="item.size"
:allow-clear="item.allowClear"
:disabled-date="item.disabledDate"
:disabled-time="item.disabledDateTime"
:show-time="item.showTime"
<template v-if="item.itemType === 'timeSelect'">
<el-time-select
v-model="formInline[item.field]"
v-bind="{...bindValue(item)}"
@change="((val) => {changeVal(val, item)})"
/>
</template>
<template v-if="item.type === 'rangePicker'">
<a-range-picker
v-model:value="formInline[item.field]"
:format="item.format"
:mode="item.mode"
:value-format="item.valueFormat"
:placeholder="item.placeholder"
:size="item.size"
:allow-clear="item.allowClear"
:disabled-date="item.disabledDate"
:disabled-time="item.disabledDateTime"
:show-time="item.showTime"
:ranges="item.ranges"
<template v-if="item.itemType === 'datePicker' || item.itemType === 'dateTimePicker'">
<el-date-picker
v-model="formInline[item.field]"
v-bind="{...bindValue(item)}"
@change="((val) => {changeVal(val, item)})"
/>
</template>
<template v-if="item.type === 'weekPicker'">
<a-week-picker
v-model:value="formInline[item.field]"
:format="item.format"
:mode="item.mode"
:value-format="item.valueFormat"
:placeholder="item.placeholder"
:size="item.size"
:allow-clear="item.allowClear"
:disabled-date="item.disabledDate"
:disabled-time="item.disabledDateTime"
:show-time="item.showTime"
@change="((val) => {changeVal(val, item)})"
/>
</template>
</a-form-item>
<a-form-item v-if="data.length > 0 && layout === 'classic'">
<a-button
</el-form-item>
<el-form-item v-if="data.length > 0 && layout === 'classic'">
<el-button
type="primary"
icon="el-icon-search"
@click="submitForm"
>
<template #icon>
<SearchOutlined />
</template>
查询
</a-button>
<a-button
</el-button>
<el-button
v-if="showReset"
icon="el-icon-refresh-right"
@click="resetForm"
>
<template #icon>
<ReloadOutlined />
</template>
重置
</a-button>
</a-form-item>
</a-form>
</a-col>
<a-col :span="layout === 'right' ? 2 : 24">
</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">
<a-button
<el-button
type="primary"
icon="el-icon-search"
@click="submitForm"
>
<template #icon>
<SearchOutlined />
</template>
查询
</a-button>
</el-button>
</div>
<div class="search__bottom--button">
<a-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"
>
<template #icon>
<ReloadOutlined />
</template>
重置
</a-button>
</el-button>
</div>
</div>
</a-col>
</a-row>
</el-col>
</el-row>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, watch, ref, unref } from 'vue'
import { SearchOutlined, ReloadOutlined } from '@ant-design/icons-vue'
import { deepClone } from '@/utils'
export default defineComponent({
name: 'Search',
components: {
SearchOutlined,
ReloadOutlined
},
props: {
// label <Col> span offset {span: 3, offset: 12} sm: {span: 3, offset: 12}
labelCol: {
type: Object as PropType<{ span: number }>,
default: () => {}
// '50px' Form form-item auto
labelWidth: {
type: String as PropType<string>,
default: ''
},
// 使 labelCol
wrapperCol: {
type: Object as PropType<{ span: number }>,
default: () => {}
},
// label
labelAlign: {
type: String as PropType<'left' | 'right'>,
labelPosition: {
type: String as PropType<'right' | 'left' | 'top'>,
default: 'right'
},
//
hideRequiredMark: {
hideRequiredAsterisk: {
type: Boolean as PropType<boolean>,
default: true
},
@ -305,6 +236,17 @@ export default defineComponent({
}
)
function bindValue(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
@ -315,10 +257,15 @@ export default defineComponent({
const form = unref(ruleForm) as any
if (!form) return
try {
const data = await form.validate()
if (data) {
emit('search-submit', data)
}
form.validate((valid: boolean) => {
if (valid) {
console.log(valid)
emit('search-submit', unref(formInline))
} else {
console.log('error submit!!')
return false
}
})
} catch (err) {
console.log(err)
}
@ -328,19 +275,20 @@ export default defineComponent({
const form = unref(ruleForm) as any
if (!form) return
await form.resetFields()
emit('reset-submit', formInline.value)
emit('reset-submit', unref(formInline))
}
function changeVal(val: any, item: any): void {
if (item.onChange) {
emit('change', {
field: item.field,
value: formInline.value[item.field]
value: unref(formInline.value[item.field])
})
}
}
return {
bindValue,
ruleForm,
formInline,
submitForm,

View File

@ -1,72 +1,101 @@
<template>
<div class="setting__content">
<div class="setting__title">导航栏布局</div>
<div class="icon__wrap">
<span :class="{'icon--active': layout==='Classic'}" @click="setLayout('Classic')">
<el-tooltip effect="dark" content="经典布局" placement="bottom">
<svg-icon icon-class="layout-classic" class="setting-svg-icon" />
</el-tooltip>
</span>
<span :class="{'icon--active': layout==='LeftTop'}" @click="setLayout('LeftTop')">
<el-tooltip effect="dark" content="左侧顶部布局" placement="bottom">
<svg-icon icon-class="layout-topLeft" class="setting-svg-icon" />
</el-tooltip>
</span>
<span :class="{'icon--active': layout==='Top'}" @click="setLayout('Top')">
<el-tooltip effect="dark" content="顶部布局" placement="bottom">
<svg-icon icon-class="layout-top" class="setting-svg-icon" />
</el-tooltip>
</span>
</div>
<div class="setting__title">侧边菜单主题</div>
<div class="setting__title">顶部菜单主题</div>
<div class="setting__title">界面功能</div>
<!-- <div class="setting__item">
<span>固定顶部操作栏</span>
<el-switch v-model="fixedNavbar" @change="setFixedNavbar" />
</div>
<div class="setting__item">
<span>固定标签页</span>
<el-switch v-model="fixedTags" @change="setFixedTags" />
</div> -->
<div class="setting__item">
<span>固定Header</span>
<el-switch v-model="fixedHeader" @change="setFixedHeader" />
</div>
<div class="setting__title">界面显示</div>
<div v-if="layout !== 'Top'" class="setting__item">
<span>顶部操作栏</span>
<el-switch v-model="navbar" @change="setNavbar" />
</div>
<div v-if="layout !== 'Top'" class="setting__item">
<span>侧边栏缩收</span>
<el-switch v-model="hamburger" @change="setHamburger" />
</div>
<div v-if="layout !== 'Top'" class="setting__item">
<span>面包屑</span>
<el-switch v-model="breadcrumb" @change="setBreadcrumb" />
</div>
<div class="setting__item">
<span>全屏按钮</span>
<el-switch v-model="screenfull" @change="setScreenfull" />
</div>
<div class="setting__item">
<span>用户头像</span>
<el-switch v-model="userInfo" @change="setUserInfo" />
</div>
<div class="setting__item">
<span>标签页</span>
<el-switch v-model="tagsView" @change="setTagsView" />
</div>
<div class="setting__item">
<span>LOGO</span>
<el-switch v-model="logo" @change="setLogo" />
</div>
<div class="setting__wrap" @click="toggleClick">
<i class="el-icon-setting" />
</div>
<el-drawer
v-model="drawer"
direction="rtl"
size="20%"
>
<template #title>
<div class="setting__title">项目配置</div>
</template>
<div class="setting__content">
<div class="setting__title">导航栏布局</div>
<div class="icon__wrap">
<span :class="{'icon--active': layout==='Classic'}" @click="setLayout('Classic')">
<el-tooltip effect="dark" content="经典布局" placement="bottom">
<svg-icon icon-class="layout-classic" class="setting-svg-icon" />
</el-tooltip>
</span>
<span :class="{'icon--active': layout==='LeftTop'}" @click="setLayout('LeftTop')">
<el-tooltip effect="dark" content="左侧顶部布局" placement="bottom">
<svg-icon icon-class="layout-topLeft" class="setting-svg-icon" />
</el-tooltip>
</span>
<span :class="{'icon--active': layout==='Top'}" @click="setLayout('Top')">
<el-tooltip effect="dark" content="顶部布局" placement="bottom">
<svg-icon icon-class="layout-top" class="setting-svg-icon" />
</el-tooltip>
</span>
</div>
<!-- <div class="setting__title">侧边菜单主题</div>
<div class="setting__title">顶部菜单主题</div> -->
<!-- <div class="setting__title">界面功能</div> -->
<!-- <div class="setting__item">
<span>固定顶部操作栏</span>
<el-switch v-model="fixedNavbar" @change="setFixedNavbar" />
</div>
<div class="setting__item">
<span>固定标签页</span>
<el-switch v-model="fixedTags" @change="setFixedTags" />
</div> -->
<div class="setting__title">界面显示</div>
<div class="setting__item">
<span>固定Header</span>
<el-switch v-model="fixedHeader" @change="setFixedHeader" />
</div>
<div v-if="layout !== 'Top'" class="setting__item">
<span>顶部操作栏</span>
<el-switch v-model="navbar" @change="setNavbar" />
</div>
<div v-if="layout !== 'Top'" class="setting__item">
<span>侧边栏缩收</span>
<el-switch v-model="hamburger" @change="setHamburger" />
</div>
<div v-if="layout !== 'Top'" class="setting__item">
<span>面包屑</span>
<el-switch v-model="breadcrumb" @change="setBreadcrumb" />
</div>
<div class="setting__item">
<span>全屏按钮</span>
<el-switch v-model="screenfull" @change="setScreenfull" />
</div>
<div class="setting__item">
<span>用户头像</span>
<el-switch v-model="userInfo" @change="setUserInfo" />
</div>
<div class="setting__item">
<span>标签页</span>
<el-switch v-model="tagsView" @change="setTagsView" />
</div>
<div class="setting__item">
<span>LOGO</span>
<el-switch v-model="logo" @change="setLogo" />
</div>
<div class="setting__item">
<span>页面标题</span>
<el-input v-model="title" size="mini" @change="setTitle" />
</div>
<div class="setting__item">
<span>LOGO标题</span>
<el-input v-model="logoTitle" size="mini" @change="setLogoTitle" />
</div>
</div>
</el-drawer>
</template>
<script lang="ts">
@ -75,6 +104,11 @@ import { appStore } from '_p/index/store/modules/app'
export default defineComponent({
name: 'Setting',
setup() {
const drawer = ref<boolean>(false)
function toggleClick(): void {
drawer.value = !drawer.value
}
const layout = computed(() => appStore.layout)
function setLayout(mode: 'Classic' | 'LeftTop' | 'Top' | 'Test') {
if (mode === layout.value) return
@ -132,7 +166,18 @@ export default defineComponent({
appStore.SetShowLogo(logo)
}
const title = ref<string>(appStore.title)
function setTitle(title: string) {
appStore.SetTitle(title)
}
const logoTitle = ref<string>(appStore.logoTitle)
function setLogoTitle(logoTitle: string) {
appStore.SetLogoTitle(logoTitle)
}
return {
drawer, toggleClick,
layout, setLayout,
// fixedNavbar, setFixedNavbar,
// fixedTags, setFixedTags,
@ -143,13 +188,35 @@ export default defineComponent({
screenfull, setScreenfull,
userInfo, setUserInfo,
tagsView, setTagsView,
logo, setLogo
logo, setLogo,
title, setTitle,
logoTitle, setLogoTitle
}
}
})
</script>
<style lang="less" scoped>
//
.setting__wrap {
position: fixed;
top: 45%;
right: 0;
z-index: 10;
display: flex;
padding: 10px;
color: #fff;
cursor: pointer;
background: #018ffb;
border-radius: 6px 0 0 6px;
justify-content: center;
align-items: center;
}
.setting__title {
font-weight: bold;
color: black;
}
//
.setting__content {
background: @appBg;
padding: 0 20px;
@ -181,6 +248,10 @@ export default defineComponent({
display: flex;
justify-content: space-between;
margin: 16px 0;
align-items: center;
&>span {
min-width: 100px;
}
}
}
</style>

View File

@ -1,94 +0,0 @@
import { defineComponent, PropType, computed } from 'vue'
import { Table } from 'ant-design-vue'
export default defineComponent({
name: 'ComTable',
props: {
columns: {
type: Array as PropType<any[]>,
default: () => []
}
},
setup(props, { attrs, slots }) {
const getBindValue = computed((): any => {
const bindValue = { ...attrs, ...props }
delete bindValue.columns
return bindValue
})
function renderTabelItem(columns: any[]) {
return columns.map((v: any) => {
const vSlots: any = v.slots || {}
if (v.children) {
const slotData = {
title: () => vSlots.title && slots[vSlots.title] && slots[vSlots.title]!(),
default: () => {renderTabelItem(v.children)}
}
if (!vSlots.title) {
delete slotData.title
}
return (
<Table.ColumnGroup
v-slots={{...slotData}}
>
</Table.ColumnGroup>
)
} else {
const slotData = {
title: () => vSlots.title && slots[vSlots.title] && slots[vSlots.title]!(),
default: ({ text, record, index, column }: any) => {
if (vSlots.customRender) {
return slots[vSlots.customRender] && slots[vSlots.customRender]!({ text, record, index, column })
} else {
return text
}
},
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters, column }: any) => vSlots.filterDropdown && slots[vSlots.filterDropdown] && slots[vSlots.filterDropdown]!({ setSelectedKeys, selectedKeys, confirm, clearFilters, column }),
filterIcon: (filtered: any) => vSlots.filterIcon && slots[vSlots.filterIcon] && slots[vSlots.filterIcon]!(filtered)
}
if (!vSlots.title) {
delete slotData.title
}
if (!vSlots.filterDropdown) {
delete slotData.filterDropdown
}
if (!vSlots.filterIcon) {
delete slotData.filterIcon
}
return (
<Table.Column
{...v}
v-slots={{...slotData}}
>
</Table.Column>
)
}
})
}
return () => {
const tableSlot = {
title: (currentPageData: any) => slots.title && slots.title(currentPageData),
footer: (currentPageData: any) => slots.footer && slots.footer(currentPageData),
expandedRowRender: ({ record, index, indent, expanded }: any) => slots.expandedRowRender && slots.expandedRowRender({ record, index, indent, expanded }),
}
if (!slots.title) {
delete tableSlot.title
}
if (!slots.footer) {
delete tableSlot.footer
}
if (!slots.expandedRowRender) {
delete tableSlot.expandedRowRender
}
return (
<Table
{...(getBindValue as any)}
v-slots={tableSlot}
>
{renderTabelItem(props.columns)}
</Table>
)
}
}
})

View File

@ -1,53 +0,0 @@
<template>
<template v-if="item.children" />
<template v-else>
<a-table-column
v-bind="getItemBindValue(item)"
>
<!-- title slot -->
<template v-if="item.slots && item.slots.title" #title>
<slot :name="item.slots.title" />
</template>
<!-- default slot -->
<template v-if="item.slots && item.slots.customRender" #default="{ text, record, index, column }">
<slot :name="item.slots.customRender" :text="text" :record="record" :index="index" :column="column" />
</template>
<!-- filterDropdown slot -->
<template v-if="item.slots && item.slots.filterDropdown" #filterDropdown="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }">
<slot :name="item.slots.filterDropdown" :setSelectedKeys="setSelectedKeys" :selectedKeys="selectedKeys" :confirm="confirm" :clearFilters="clearFilters" :column="column" />
</template>
<!-- filterIcon slot -->
<template v-if="item.slots && item.slots.filterIcon" #filterIcon="filtered">
<slot :name="item.slots.filterIcon" :filtered="filtered" />
</template>
</a-table-column>
</template>
</template>
<script lang="ts">
import { defineComponent, PropType, computed } from 'vue'
export default defineComponent({
name: 'TableItem',
functional: true,
props: {
item: {
type: Object as PropType<object>,
required: true
}
},
setup(props, { attrs }) {
alert(',,,,')
const getItemBindValue = computed(() => {
return function(item: any) {
return { ...item, ...attrs, ...props }
}
})
return {
getItemBindValue
}
}
})
</script>
<style>
</style>

View File

@ -0,0 +1,35 @@
<script lang="ts">
import { defineComponent, inject, h, PropType } from 'vue'
export default defineComponent({
name: 'Slot',
props: {
row: {
type: Object as PropType<object>,
default: () => null
},
index: {
type: Number as PropType<number>,
default: null
},
column: {
type: Object as PropType<object>,
default: () => null
},
slotName: {
type: String as PropType<string>,
default: ''
}
},
render(props: any) {
const _this: any = inject('tableRoot')
return h('div', _this.slots[props.slotName]({
row: props.row,
column: props.column,
index: props.index
}))
}
})
</script>
<style>
</style>

View File

@ -0,0 +1,84 @@
<template>
<el-table-column v-bind="{...bindValue(child)}" :prop="child.key">
<template v-for="item in child.children">
<!-- 树型数据 -->
<template v-if="item.children && item.children.length">
<table-column
:key="item[item.key]"
:child="item"
/>
</template>
<template v-else>
<el-table-column
:key="item[item.key]"
v-bind="{...bindValue(item)}"
:prop="item.key"
>
<!-- 表头插槽 -->
<template v-if="item.slots && item.slots.header" #header="scope">
<table-slot
v-if="item.slots && item.slots.header"
:slot-name="item.slots.header"
:row="scope.row"
:column="item"
:index="scope.$index"
/>
</template>
<!-- 表格内容插槽自定义 -->
<template #default="scope">
<table-slot
v-if="item.slots && item.slots.default"
:slot-name="item.slots.default"
:row="scope.row"
:column="item"
:index="scope.$index"
/>
<!-- 不需要插槽 -->
<div v-else style="display: inline-block;">
{{ scope.row[item.key] }}
</div>
</template>
</el-table-column>
</template>
</template>
</el-table-column>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
import TableSlot from './Slot.vue'
import { deepClone } from '@/utils'
export default defineComponent({
name: 'TableColumn',
components: {
TableSlot
},
props: {
child: {
type: Object as PropType<object>,
default: () => null,
required: true
}
},
setup() {
function bindValue(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
}
return {
bindValue
}
}
})
</script>
<style>
</style>

View File

@ -1,3 +0,0 @@
import Table from './Table'
export default Table

View File

@ -1,47 +1,109 @@
<template>
<a-table v-bind="getBindValue">
<table-item
v-for="item in columns"
:key="item.key || item.dataIndex"
:item="item"
<el-table ref="elTable" v-bind="getBindValue">
<!-- 多选 -->
<el-table-column
v-if="selection"
type="selection"
width="55"
/>
<template v-if="slots.title" #title="currentPageData">
<slot name="title" :currentPageData="currentPageData" />
</template>
<!-- <template v-for="item in columns" :key="item.key || item.dataIndex"> -->
<template v-for="item in columns">
<!-- 树型数据 -->
<template v-if="item.children && item.children.length">
<table-column
:key="item[item.key]"
:child="item"
/>
</template>
<!-- </template> -->
<template v-if="slots.footer" #footer="currentPageData">
<slot name="footer" :currentPageData="currentPageData" />
<template v-else>
<el-table-column
:key="item[item.key]"
v-bind="{...bindValue(item)}"
:prop="item.key"
>
<!-- 表头插槽 -->
<template v-if="item.slots && item.slots.header" #header="scope">
<table-slot
v-if="item.slots && item.slots.header"
:slot-name="item.slots.header"
:row="scope.row"
:column="item"
:index="scope.$index"
/>
</template>
<!-- 表格内容插槽自定义 -->
<template #default="scope">
<table-slot
v-if="item.slots && item.slots.default"
:slot-name="item.slots.default"
:row="scope.row"
:column="item"
:index="scope.$index"
/>
<!-- 不需要插槽 -->
<div v-else style="display: inline-block;">
{{ scope.row[item.key] }}
</div>
</template>
</el-table-column>
</template>
</template>
</a-table>
</el-table>
</template>
<script lang="ts">
import { defineComponent, PropType, computed } from 'vue'
import TableItem from './TableItem.vue'
import { defineComponent, PropType, computed, provide, getCurrentInstance, ref, unref } from 'vue'
import { deepClone } from '@/utils'
import TableColumn from './components/TableColumn.vue'
import TableSlot from './components/Slot.vue'
export default defineComponent({
name: 'ComTable',
components: {
TableItem
TableSlot,
TableColumn
},
props: {
columns: {
type: Array as PropType<any[]>,
default: () => []
},
selection: {
type: Boolean as PropType<boolean>,
default: false
}
},
setup(props, { attrs, slots }) {
console.log(TableItem)
const elTable = ref<HTMLElement | null>(null)
function getTableRef() {
return unref(elTable as any)
}
const _this = getCurrentInstance() as any
provide('tableRoot', _this)
const getBindValue = computed((): any => {
const bindValue = { ...attrs, ...props }
delete bindValue.columns
return bindValue
})
function bindValue(item: any) {
const delArr: string[] = []
const obj = deepClone(item)
for (const key in obj) {
if (delArr.indexOf(key) !== -1) {
delete obj[key]
}
}
return obj
}
return {
getBindValue,
slots
elTable,
getBindValue, bindValue,
slots,
getTableRef
}
}
})

View File

@ -1,6 +1,6 @@
import Clipboard from 'clipboard'
import { Directive, DirectiveBinding } from 'vue'
import { message } from 'ant-design-vue'
import { ElMessage } from 'element-plus'
if (!Clipboard) {
throw new Error('you should npm install `clipboard` --save at first ')
@ -47,7 +47,7 @@ function createdClipboard(el: HTMLElement | any, arg: string | undefined, value:
if (callback) {
callback(e)
} else {
message.success('复制成功')
ElMessage.success('复制成功')
}
})
clipboard.on('error', e => {
@ -55,7 +55,7 @@ function createdClipboard(el: HTMLElement | any, arg: string | undefined, value:
if (callback) {
callback(e)
} else {
message.success('复制失败')
ElMessage.success('复制失败')
}
})
el._v_clipboard = clipboard

View File

@ -92,6 +92,12 @@ import {
ElNotification
} from 'element-plus'
import locale from 'element-plus/lib/locale'
import lang from 'element-plus/lib/locale/lang/zh-cn'
// 设置语言
locale.use(lang)
const components = [
ElAlert,
ElAside,

View File

@ -1,6 +1,6 @@
import request from './request'
import config from '../config'
import config from './config'
import { AxiosPromise, ResponseType } from 'axios'

View File

@ -1,39 +1,9 @@
/**
*
* request全局配
*/
import { ConfigOptions } from './types'
const config: ConfigOptions = {
/**
* title
*/
title: 'vue-antdv-admin',
/**
*
*/
has_tags: true,
/**
* logo
*/
show_logo: true,
/**
* logo标题
*/
logo_title: 'vue-antdv-admin',
/**
* Classic() Top() LeftTop()
*/
layout: 'Test',
/**
* light() dark()
*/
theme: 'dark',
/**
* api请求基础路径
*/
@ -61,12 +31,7 @@ const config: ConfigOptions = {
*
* application/x-www-form-urlencoded multipart/form-data
*/
default_headers: 'application/json',
/**
* -
*/
user_info: 'userInfo'
default_headers: 'application/json'
}
export default config

View File

@ -0,0 +1,9 @@
/**
* request配置
*/
export interface ConfigOptions {
base_url: object
result_code: number | string
default_headers: string
request_timeout: number
}

View File

@ -1,10 +1,10 @@
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
import { message } from 'ant-design-vue'
import { ElMessage } from 'element-plus'
import qs from 'qs'
import config from '../config'
import config from './config'
const { result_code, base_url } = config
@ -37,12 +37,12 @@ service.interceptors.response.use(
if (response.data.code === result_code) {
return response.data
} else {
message.error(response.data.message)
ElMessage.error(response.data.message)
}
},
(error: AxiosError) => {
console.log('err' + error) // for debug
message.error(error.message)
ElMessage.error(error.message)
return Promise.reject(error)
}
)

View File

@ -1,16 +0,0 @@
/**
*
*/
export interface ConfigOptions {
title?: string
has_tags: boolean
show_logo: boolean
logo_title: string
base_url: object
result_code: number | string
default_headers: string
request_timeout: number
user_info: string
layout: 'Classic' | 'Top' | 'LeftTop' | 'Test'
theme: 'light' | 'dark'
}

View File

@ -1,77 +0,0 @@
<template>
<router-link :class="['app-logo', 'app-logo-' + theme]" to="/">
<img :src="require('@/assets/img/logo.png')">
<div v-if="show" class="sidebar-title">{{ title }}</div>
</router-link>
</template>
<script lang="ts">
import { defineComponent, ref, watch, PropType } from 'vue'
import config from '_p/index/config'
export default defineComponent({
name: 'Logo',
props: {
collapsed: {
type: Boolean as PropType<boolean>,
required: true
},
theme: {
type: String as PropType<'light' | 'dark'>,
default: 'dark'
}
},
setup(props) {
const show = ref<boolean>(true)
watch(
() => props.collapsed,
(collapsed: boolean) => {
if (!collapsed) {
setTimeout(() => {
show.value = !collapsed
}, 400)
} else {
show.value = !collapsed
}
}
)
return {
show,
title: config.title
}
}
})
</script>
<style lang="less" scoped>
.app-logo {
display: flex;
align-items: center;
padding-left: 18px;
cursor: pointer;
height: @topSilderHeight;
max-width: 200px;
img {
width: 37px;
height: 37px;
}
.sidebar-title {
font-size: 14px;
font-weight: 700;
transition: .5s;
margin-left: 12px;
}
}
.app-logo-dark {
background-color: @menuBg;
.sidebar-title {
color: #fff;
}
}
.app-logo-light {
background-color: #fff;
.sidebar-title {
color: #000;
}
}
</style>

View File

@ -1,75 +0,0 @@
<template>
<div class="navbar">
<hamburger :collapsed="collapsed" class="hamburger-container" @toggleClick="setCollapsed" />
<breadcrumb class="breadcrumb-container" />
<div class="right-menu">
<screenfull class="right-menu-item hover-effect" />
<user-info class="right-menu-item hover-effect" />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from 'vue'
import Hamburger from '_c/Hamburger/index.vue'
import Breadcrumb from '_c/Breadcrumb/index.vue'
import Screenfull from '_c/Screenfull/index.vue'
import UserInfo from './UserInfo.vue'
import { appStore } from '_p/index/store/modules/app'
export default defineComponent({
name: 'Navbar',
components: {
Hamburger,
Breadcrumb,
Screenfull,
UserInfo
},
setup() {
const collapsed = computed(() => appStore.collapsed)
function setCollapsed(collapsed: boolean): void {
appStore.SetCollapsed(collapsed)
}
return {
collapsed,
setCollapsed
}
}
})
</script>
<style lang="less" scoped>
.navbar {
.hamburger-container {
line-height: @navbarHeight;
float: left;
cursor: pointer;
margin-left: 15px;
&:hover {
background: rgba(0, 0, 0, .025);
}
}
.breadcrumb-container {
float: left;
}
.right-menu {
float: right;
height: 100%;
line-height: @navbarHeight;
&:focus {
outline: none;
}
.right-menu-item {
display: inline-block;
padding: 0 8px;
height: 100%;
font-size: 18px;
color: #5a5e66;
vertical-align: text-bottom;
}
}
}
</style>

View File

@ -1,34 +0,0 @@
<template>
<div>
<i v-if="icon.includes('el-icon')" :class="[icon, 'sub-el-icon', 'anticon']" />
<svg-icon v-else :icon-class="icon" class="anticon" />
<slot name="title">
<span class="anticon-item">{{ title }}</span>
</slot>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
export default defineComponent({
name: 'Item',
props: {
icon: {
type: String as PropType<string>,
default: ''
},
title: {
type: String as PropType<string>,
default: ''
}
}
})
</script>
<style lang="less" scoped>
.anticon-item {
opacity: 1;
transition: opacity .3s cubic-bezier(.645,.045,.355,1),width .3s cubic-bezier(.645,.045,.355,1);
}
</style>

View File

@ -1,92 +0,0 @@
import { ref } from 'vue'
import type { RouteRecordRaw } from 'vue-router'
import path from 'path'
import { isExternal } from '@/utils/validate'
// import { setSidebarItem } from './types'
export function setSidebarItem() {
const onlyOneChild = ref<any>(null)
function hasOneShowingChild(children: RouteRecordRaw[] = [], parent: RouteRecordRaw): boolean {
const showingChildren: RouteRecordRaw[] = children.filter((item: RouteRecordRaw) => {
if (item.meta && item.meta.hidden) {
return false
} else {
// Temp set(will be used if only has one showing child)
onlyOneChild.value = item
return true
}
})
// When there is only one child router, the child router is displayed by default
if (showingChildren.length === 1) {
return true
}
// Show parent if there are no child router to display
if (showingChildren.length === 0) {
onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }
return true
}
return false
}
function resolvePath(basePath: string, routePath: string): string {
if (isExternal(routePath)) {
return routePath
}
return path.resolve(basePath, routePath)
}
function treeFindRouter(tree: any[], func: Function, result: RouteRecordRaw[] = []): RouteRecordRaw[] {
if (!tree) return []
for (const data of tree) {
result.push(data)
if (func(data)) return result
if (data.children) {
const findChildren = treeFindRouter(data.children, func, result)
if (findChildren.length) return findChildren
}
result.pop()
}
return []
}
function getFullPath(arr: string[]): string[] {
const result: string[] = []
let basePath = '/'
for (let i = 0; i < arr.length; i++) {
if (i === arr.length) {
continue
}
result.push(resolvePath(basePath, arr[i]))
basePath = resolvePath(basePath, arr[i])
}
return result
}
function findCurrentRoute(routers: RouteRecordRaw[], path: string, basePath = '/', result: Array<any> = []): any {
for (const item of routers) {
if (!item.meta?.hidden) {
const _basePath = resolvePath(basePath, item.path)
if (_basePath === path && !item.children) {
result.push(item)
} else {
if (item.children) {
findCurrentRoute(item.children, path, _basePath, result)
}
}
}
}
return result ? result[0] : null
}
return {
onlyOneChild,
hasOneShowingChild,
resolvePath,
treeFindRouter,
getFullPath,
findCurrentRoute
}
}

View File

@ -1,10 +0,0 @@
import type { Ref } from 'vue'
import ref from 'vue'
export interface setSidebarItem = {
onlyOneChild: Ref<any | null>
hasOneShowingChild: Function<boolean>
resolvePath: Function<string>
treeFindPath: Function<string[]>
getFullPath: Function<string[]>
}

View File

@ -1,10 +0,0 @@
.sidebar-container {
height: 100%;
}
.has-logo {
height: calc(~"100% - @{topSilderHeight}");
}
.menu-wrap {
height: 100%;
overflow: hidden;
}

View File

@ -1,3 +0,0 @@
import Silder from './index'
export default Silder

View File

@ -1,176 +0,0 @@
import { ref, defineComponent, PropType, computed, watch } from 'vue'
// import { Menu } from 'element-plus'
import { useRouter } from 'vue-router'
import type { RouteRecordRaw, RouteLocationNormalizedLoaded } from 'vue-router'
import Scrollbar from '_c/Scrollbar/index.vue'
import Item from './Item.vue'
import { permissionStore } from '_p/index/store/modules/permission'
import { setSidebarItem } from './hooks/setSidebarItem'
import { isExternal } from '@/utils/validate'
import { findIndex } from '@/utils'
import config from '_p/index/config'
const { show_logo } = config
import './index.less'
export default defineComponent({
name: 'Silder',
components: {
Scrollbar,
Item
},
props: {
collapsed: {
type: Boolean as PropType<boolean>,
default: true
},
inlineIndent: {
type: Number as PropType<number>,
default: 24
},
forceSubMenuRender: {
type: Boolean as PropType<boolean>,
default: false
},
mode: {
type: String as PropType<'vertical' | 'horizontal' | 'vertical-right' | 'inline'>,
default: 'inline'
},
theme: {
type: String as PropType<'light' | 'dark'>,
default: 'dark'
}
},
setup(props) {
const { currentRoute, push } = useRouter()
const { resolvePath, treeFindRouter, getFullPath, findCurrentRoute } = setSidebarItem()
const routers = computed((): RouteRecordRaw[] => {
return permissionStore.routers
})
const selectedKeys = ref<string[]>([])
const openKeys = ref<string[]>([])
const activeMenuName = ref<string>('')
watch(
() => currentRoute.value,
(route: RouteLocationNormalizedLoaded) => {
setSelectedKeys(route)
},
{
immediate: true
}
)
watch(
() => props.collapsed,
(collapsed: boolean) => {
setOpenKeys(currentRoute.value, collapsed)
},
{
immediate: true
}
)
function handleOpenChange(keyArr: string[]) {
// 需要自己管理openKeys
openKeys.value = keyArr
}
function setOpenKeys(route: RouteLocationNormalizedLoaded, collapsed: boolean) {
let currentRoute: any = null
if (route.meta.activeMenu) {
currentRoute = findCurrentRoute(routers.value, route.meta.activeMenu)
} else {
currentRoute = route
}
const parentRoute: RouteRecordRaw[] = treeFindRouter(permissionStore.routers, (data: any) => data.name === currentRoute.name)
openKeys.value = collapsed ? [] : getFullPath(parentRoute.map((v: RouteRecordRaw) => v.path))
}
function setSelectedKeys(route: RouteLocationNormalizedLoaded) {
const { meta, path, name } = route
activeMenuName.value = name as string
if (meta.activeMenu) {
selectedKeys.value = [meta.activeMenu]
return
}
selectedKeys.value = [path]
}
function highlightMenu(className: string, basePath: string) {
const parentRoute: RouteRecordRaw[] = treeFindRouter(permissionStore.routers, (data: any) => data.name === currentRoute.value.name)
const parentFullPath: string[] = getFullPath(parentRoute.map((v: RouteRecordRaw) => v.path))
return findIndex(parentFullPath, (item: string) => item === basePath) !== -1 ? className : ''
}
function handleMenuClick({ key }: any) {
console.log(key)
if (isExternal(key)) {
window.open(key)
} else {
push({ path: key })
}
}
// function renderMenu(routers: RouteRecordRaw[], basePath = '/') {
// if (routers && routers.length) {
// return routers.map((item: RouteRecordRaw) => {
// if (!item.meta?.hidden) {
// const _basePath = resolvePath(basePath, item.path)
// const { onlyOneChild, hasOneShowingChild } = setSidebarItem()
// if (hasOneShowingChild(item.children, item) && (!onlyOneChild.value.children || onlyOneChild.value.noShowingChildren) && !item.meta?.alwaysShow) {
// return (
// <Menu.Item key={resolvePath(_basePath, onlyOneChild.value.path)} v-slots={{
// default: () => [
// <Item
// icon={onlyOneChild.value.meta && onlyOneChild.value.meta.icon}
// title={onlyOneChild.value.meta?.title}
// />
// ]
// }}>
// </Menu.Item>
// )
// } else {
// return (
// <Menu.SubMenu
// key={_basePath}
// class={highlightMenu(props.theme + '-active-item', _basePath)}
// popupClassName={highlightMenu(props.theme + '-popup-active-item', _basePath)}
// v-slots={{
// title: () => [
// <Item
// icon={item.meta && item.meta.icon}
// title={item.meta?.title}
// />
// ]
// }}
// >
// {renderMenu(item.children as any, _basePath)}
// </Menu.SubMenu>
// )
// }
// }
// })
// }
// }
// return () => {
// return (
// // <div class={{ 'has-logo': show_logo, 'sidebar-container': true }}>
// // <scrollbar class='menu-wrap'>
// // <Menu
// // selectedKeys={selectedKeys.value}
// // openKeys={openKeys.value}
// // theme={props.theme}
// // mode={props.mode}
// // onClick={handleMenuClick}
// // onOpenChange={handleOpenChange}
// // >
// // {renderMenu(routers.value)}
// // </Menu>
// // </scrollbar>
// // </div>
// )
// }
}
})

View File

@ -1,377 +0,0 @@
<template>
<div ref="wrapper" class="tags-view-container">
<span class="move-btn prev-btn">
<a-button @click="move(-200)">
<template #icon><LeftOutlined /></template>
</a-button>
</span>
<scroll-pane ref="scrollPane" class="tags-view-wrapper">
<div>
<router-link
v-for="tag in visitedViews"
:ref="setTagRef"
:key="tag.path"
:class="isActive(tag) ? 'active' : ''"
:to="{ path: tag.path, query: tag.query, fullPath: tag.fullPath }"
tag="span"
class="tags-view-item"
@click.middle="closeSelectedTag(tag)"
@contextmenu.prevent="openMenu(tag, $event)"
>
{{ tag.title }}
<CloseOutlined v-if="!tag.meta.affix" class="icon-close" @click.prevent.stop="closeSelectedTag(tag)" />
</router-link>
</div>
</scroll-pane>
<span class="move-btn next-btn">
<a-button @click="move(200)">
<template #icon><RightOutlined /></template>
</a-button>
</span>
<ul v-show="visible" :style="{left: left + 'px', top: top + 'px'}" class="contextmenu">
<li @click="refreshSelectedTag(selectedTag)">
刷新
</li>
<li v-if="!(selectedTag.meta&&selectedTag.meta.affix)" @click="closeSelectedTag(selectedTag)">
关闭
</li>
<li @click="closeOthersTags">
关闭其他
</li>
<li @click="closeAllTags(selectedTag)">
关闭全部
</li>
</ul>
</div>
</template>
<script lang="ts">
import ScrollPane from '_c/ScrollPane/index.vue'
import path from 'path'
import { defineComponent, ref, unref, computed, watch, onMounted, nextTick } from 'vue'
import { tagsViewStore } from '_p/index/store/modules/tagsView'
import { permissionStore } from '_p/index/store/modules/permission'
import { useRouter } from 'vue-router'
import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
// import { LeftOutlined, RightOutlined, CloseOutlined } from '@ant-design/icons-vue'
export default defineComponent({
name: 'TagsView',
components: {
ScrollPane,
// CloseOutlined,
// LeftOutlined,
// RightOutlined
},
setup() {
const { currentRoute, push, replace } = useRouter()
const wrapper = ref<HTMLElement | null>(null)
const scrollPane = ref<HTMLElement | null>(null)
const visible = ref<boolean>(false)
const top = ref<number>(0)
const left = ref<number>(0)
const selectedTag = ref<any>({})
const affixTags = ref<any[]>([])
const visitedViews = computed(() => tagsViewStore.visitedViews)
const routers = computed(() => permissionStore.routers)
const tagRefs = ref<any[]>([])
function setTagRef(el: any): void {
tagRefs.value.push(el)
}
function isActive(route: RouteLocationNormalizedLoaded): boolean {
return route.path === currentRoute.value.path
}
function filterAffixTags(routes: RouteRecordRaw[], basePath = '/'): any[] {
let tags: any[] = []
routes.forEach((route: RouteRecordRaw) => {
if (route.meta && route.meta.affix) {
const tagPath = path.resolve(basePath, route.path)
tags.push({
fullPath: tagPath,
path: tagPath,
name: route.name,
meta: { ...route.meta }
})
}
if (route.children) {
const tempTags: any[] = filterAffixTags(route.children, route.path)
if (tempTags.length >= 1) {
tags = [...tags, ...tempTags]
}
}
})
return tags
}
function initTags(): void {
affixTags.value = filterAffixTags(routers.value)
const affixTagArr: any[] = affixTags.value
for (const tag of affixTagArr) {
// Must have tag name
if (tag.name) {
tagsViewStore.addVisitedView(tag)
}
}
}
function addTags(): void | boolean {
const { name } = currentRoute.value
if (name) {
tagsViewStore.addView(currentRoute.value)
}
return false
}
function moveToCurrentTag() {
// TODO tagRefs
tagRefs.value = []
const tags = unref(tagRefs)
nextTick(() => {
for (const tag of tags) {
if (tag && tag.to.path === currentRoute.value.path) {
(unref(scrollPane) as any).moveToTarget(tag)
// when query is different then update
if (tag.to.fullPath !== currentRoute.value.fullPath) {
tagsViewStore.updateVisitedView(currentRoute.value)
}
break
}
}
})
}
async function refreshSelectedTag(view: RouteLocationNormalizedLoaded) {
await tagsViewStore.delCachedView()
const { fullPath } = view
nextTick(() => {
replace({
path: '/redirect' + fullPath
})
})
}
async function closeSelectedTag(view: RouteLocationNormalizedLoaded) {
const views: any = await tagsViewStore.delView(view)
if (isActive(view)) {
toLastView(views.visitedViews)
}
}
function closeOthersTags() {
push(selectedTag.value)
tagsViewStore.delOthersViews(selectedTag.value).then(() => {
moveToCurrentTag()
})
}
async function closeAllTags(view: RouteLocationNormalizedLoaded) {
const views: any = await tagsViewStore.delAllViews()
if (affixTags.value.some(tag => tag.path === view.path)) {
return
}
toLastView(views.visitedViews)
}
function toLastView(visitedViews: RouteLocationNormalizedLoaded[]) {
const latestView = visitedViews.slice(-1)[0]
setTimeout(() => {
if (latestView) {
push(latestView)
} else {
// You can set another route
push('/')
}
}, 100)
}
function openMenu(tag: RouteLocationNormalizedLoaded, e: any) {
const menuMinWidth = 105
const offsetLeft: number = (unref(wrapper) as any).getBoundingClientRect().left // container margin left
const offsetWidth: number = (unref(wrapper) as any).offsetWidth // container width
const maxLeft: number = offsetWidth - menuMinWidth// left boundary
const itemLeft: number = e.clientX - offsetLeft + 4
if (itemLeft > maxLeft) {
left.value = maxLeft
} else {
left.value = itemLeft
}
top.value = e.offsetY
visible.value = true
selectedTag.value = tag
}
function closeMenu() {
visible.value = false
}
function move(to: number) {
(unref(scrollPane) as any).moveTo(to)
}
onMounted(() => {
initTags()
addTags()
})
watch(
() => currentRoute.value,
() => {
addTags()
moveToCurrentTag()
}
)
watch(
() => visible.value,
(visible: boolean) => {
if (visible) {
document.body.addEventListener('click', closeMenu)
} else {
document.body.removeEventListener('click', closeMenu)
}
}
)
return {
wrapper, scrollPane,
visible, top, left,
selectedTag, affixTags,
visitedViews, routers,
tagRefs, setTagRef,
isActive,
filterAffixTags,
initTags,
addTags,
moveToCurrentTag,
refreshSelectedTag,
closeSelectedTag,
closeOthersTags,
closeAllTags,
toLastView,
openMenu,
closeMenu,
move
}
}
})
</script>
<style lang="less" scoped>
.tags-view-container {
height: @tagsViewHeight;
width: 100%;
background: #fff;
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, .12), 0 0 3px 0 rgba(0, 0, 0, .04);
position: relative;
display: flex;
z-index: 100;
&::after {
content: "";
width: 100%;
height: 1px;
border-top: #d8dce5;
position: absolute;
left: 0;
bottom: 0;
}
.move-btn {
display: inline-block;
width: @tagsViewHeight;
height: @tagsViewHeight;
line-height: @tagsViewHeight;
text-align: center;
.ant-btn {
width: @tagsViewHeight;
height: @tagsViewHeight;
line-height: @tagsViewHeight;
}
}
.tags-view-wrapper {
width: calc(~"100% - 78px");
.tags-view-item {
display: inline-block;
position: relative;
cursor: pointer;
height: 30px;
line-height: 30px;
border: 1px solid #d8dce5;
color: #495060;
background: #fff;
padding: 0 8px;
font-size: 12px;
margin-left: 5px;
&:last-of-type {
margin-right: 4px;
}
&.active {
background-color: #304156;
color: #fff;
border-color: #304156;
&::before {
content: '';
background: #fff;
display: inline-block;
width: 8px;
height: 8px;
border-radius: 50%;
position: relative;
margin-right: 5px;
}
}
.icon-close {
width: 16px;
height: 16px;
border-radius: 50%;
text-align: center;
line-height: 16px;
transition: all .3s cubic-bezier(.645, .045, .355, 1);
transform-origin: 100% 50%;
margin-left: 5px;
position: relative;
top: -1px;
&:before {
transform: scale(.6);
display: inline-block;
}
&:hover {
background-color: #b4bccc;
color: #fff;
}
}
}
}
.contextmenu {
margin: 0;
background: #fff;
z-index: 200;
position: absolute;
list-style-type: none;
padding: 5px 0;
border-radius: 4px;
font-size: 12px;
font-weight: 400;
color: #333;
box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, .3);
li {
margin: 0;
padding: 7px 16px;
cursor: pointer;
&:hover {
background: #eee;
}
}
}
}
@{deep}(.scrollbar__view) {
height: @tagsViewHeight;
line-height: @tagsViewHeight;
}
</style>

View File

@ -1,68 +0,0 @@
<template>
<el-dropdown class="avatar-container" :trigger="['hover']">
<div>
<div class="avatar-wrapper">
<img :src="require('@/assets/img/avatar.gif')" class="user-avatar">
<span class="name-item">管理员</span>
</div>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item key="1">
<router-link to="/">
首页
</router-link>
</el-dropdown-item>
<el-dropdown-item key="2">
<span style="display: block;" @click="loginOut">退出登录</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { resetRouter } from '_p/index/router'
import wsCache from '@/cache'
import { useRouter } from 'vue-router'
export default defineComponent({
name: 'UserInfo',
setup() {
const { replace } = useRouter()
async function loginOut(): Promise<void> {
wsCache.clear()
await resetRouter() //
// this.$store.dispatch('delAllViews') // tags
replace('/login')
}
return {
loginOut
}
}
})
</script>
<style lang="less" scoped>
.avatar-container {
margin-right: 30px;
padding: 0 10px;
.avatar-wrapper {
display: flex;
align-items: center;
height: 100%;
cursor: pointer;
.user-avatar {
width: 30px;
height: 30px;
border-radius: 10px;
}
.name-item {
font-size: 14px;
font-weight: 600;
display: inline-block;
margin-left: 5px;
}
}
}
</style>

View File

@ -1,29 +1,26 @@
<template>
<div class="app-wrapper">
<component :is="component" />
<component :is="layout" />
</div>
</template>
<script lang="ts">
// import Classic from './modules/Classic.vue'
// import Top from './modules/Top.vue'
// import LeftTop from './modules/LeftTop.vue'
import Test from './modules/Test.vue'
import { defineComponent, ref } from 'vue'
import config from '_p/index/config'
import { defineComponent, computed } from 'vue'
import Classic from './modules/Classic.vue'
import Top from './modules/Top.vue'
import LeftTop from './modules/LeftTop.vue'
import { appStore } from '_p/index/store/modules/app'
export default defineComponent({
name: 'Layout',
components: {
// Classic,
// Top,
// LeftTop,
Test
Classic,
Top,
LeftTop
},
setup() {
const { layout } = config
const component = ref<string>(layout)
const layout = computed(() => appStore.layout)
return {
component
layout
}
}
})

View File

@ -1,112 +1,251 @@
<template>
<a-layout class="app-wrapper">
<a-layout-sider
v-model:collapsed="collapsed"
:trigger="null"
collapsible
:class="'ant-layout-sider--' + theme"
class="sidebar-container-wrap"
<div :class="classObj" class="app__wrap">
<!-- Classic -->
<div
class="sidebar__wrap"
:class="{'sidebar__wrap--collapsed': collapsed}"
>
<logo
v-if="show_logo"
v-if="showLogo && layout === 'Classic'"
:collapsed="collapsed"
:theme="theme"
/>
<silder
:collapsed="collapsed"
:theme="theme"
/>
</a-layout-sider>
<a-layout>
<a-layout-header :class="{'ant-layout-header-collapsed': collapsed}">
<navbar />
</a-layout-header>
<a-layout-content :class="{'layout-content-has-tags':has_tags}">
<scrollbar class="main-wrap">
<tags-view :class="{'has-tags':has_tags, 'has-tags-collapsed': collapsed && has_tags}" />
<app-main class="classic-module--wrap" />
</scrollbar>
</a-layout-content>
</a-layout>
</a-layout>
<sider :layout="layout" mode="vertical" />
</div>
<div
class="main__wrap"
:class="{
'main__wrap--collapsed': collapsed
}"
>
<el-scrollbar
class="main__wrap--content"
:class="{
'main__wrap--fixed--all': fixedHeader && showNavbar && showTags,
'main__wrap--fixed--nav': fixedHeader && showNavbar && !showTags,
'main__wrap--fixed--tags': fixedHeader && !showNavbar && showTags
}"
>
<div
class="header__wrap"
:class="{
'header__wrap--fixed': fixedHeader,
'header__wrap--collapsed': fixedHeader && collapsed
}"
>
<div
v-if="showNavbar"
class="navbar__wrap"
>
<hamburger
v-if="showHamburger"
:collapsed="collapsed"
class="hover-container"
@toggleClick="setCollapsed"
/>
<breadcrumb v-if="showBreadcrumb" />
<div v-if="showScreenfull || showUserInfo" class="navbar__wrap--right">
<screenfull v-if="showScreenfull" class="hover-container screenfull-container" />
<user-info v-if="showUserInfo" class="hover-container user-container" />
</div>
</div>
<div
v-if="showTags"
class="tags__wrap"
>
<tags-view />
</div>
</div>
<app-main />
</el-scrollbar>
</div>
<!-- setting -->
<setting />
<!-- setting -->
</div>
</template>
<script lang="ts">
import { defineComponent, computed, PropType } from 'vue'
import { defineComponent, computed } from 'vue'
import { appStore } from '_p/index/store/modules/app'
import Silder from '../components/Silder'
import Navbar from '../components/Navbar.vue'
import AppMain from '../components/AppMain.vue'
import TagsView from '../components/TagsView.vue'
import Logo from '../components/Logo.vue'
import Scrollbar from '_c/Scrollbar/index.vue'
import config from '_p/index/config'
const { show_logo, has_tags, theme } = config
import TagsView from '_c/TagsView/index.vue'
import Logo from '_c/Logo/index.vue'
import Sider from '_c/Sider/index.vue'
import Hamburger from '_c/Hamburger/index.vue'
import Breadcrumb from '_c/Breadcrumb/index.vue'
import Screenfull from '_c/Screenfull/index.vue'
import UserInfo from '_c/UserInfo/index.vue'
import Setting from '_c/Setting/index.vue'
export default defineComponent({
name: 'Classic',
components: {
Silder,
Navbar,
Sider,
Hamburger,
Breadcrumb,
Screenfull,
UserInfo,
AppMain,
TagsView,
Logo,
Scrollbar
},
props: {
theme: {
type: String as PropType<'light' | 'dark'>,
default: theme
}
Setting
},
setup() {
const layout = computed(() => appStore.layout)
const collapsed = computed(() => appStore.collapsed)
const showLogo = computed(() => appStore.showLogo)
const showTags = computed(() => appStore.showTags)
const showBreadcrumb = computed(() => appStore.showBreadcrumb)
const showHamburger = computed(() => appStore.showHamburger)
const showScreenfull = computed(() => appStore.showScreenfull)
const showUserInfo = computed(() => appStore.showUserInfo)
const showNavbar = computed(() => appStore.showNavbar)
// const fixedNavbar = computed(() => appStore.fixedNavbar)
// const fixedTags = computed(() => appStore.fixedTags)
const fixedHeader = computed(() => appStore.fixedHeader)
const classObj = computed(() => {
const obj = {}
obj[`app__wrap--${layout.value}`] = true
return obj
})
function setCollapsed(collapsed: boolean): void {
appStore.SetCollapsed(collapsed)
}
return {
classObj,
layout,
collapsed,
show_logo,
has_tags
showLogo,
showTags,
showBreadcrumb,
showHamburger,
showScreenfull,
showUserInfo,
showNavbar,
fixedHeader,
// fixedNavbar,
// fixedTags,
setCollapsed
}
}
})
</script>
<style lang="less" scoped>
@{deep}(.ant-layout-header) {
line-height: @navbarHeight;
height: @navbarHeight;
position: fixed;
top: 0;
left: @menuWidth;
width: calc(~"100% - @{menuWidth}");
padding: 0;
background: #fff;
transition: all 0.2s;
}
.ant-layout-sider--light {
background: @menuLightBg;
}
.ant-layout-sider--dark {
background: @menuBg;
}
.ant-layout-content {
margin-top: @navbarHeight;
.mian-wrap {
background-color: @contentBg;
.app__wrap {
position: relative;
height: 100%;
width: 100%;
.sidebar__wrap {
position: fixed;
width: @menuWidth;
top: 0;
left: 0;
height: 100%;
transition: width 0.2s;
}
.sidebar__wrap--collapsed {
width: @menuMinWidth;
@{deep}(.anticon-item) {
display: none;
}
}
.main__wrap {
position: absolute;
width: calc(~"100% - @{menuWidth}");
height: 100%;
top: 0;
left: @menuWidth;
transition: all 0.2s;
z-index: 1;
.header__wrap {
transition: all 0.2s;
.navbar__wrap {
display: flex;
align-items: center;
height: @navbarHeight;
padding: 0 20px 0 15px;
position: relative;
background: @contentBg;
&:after {
content: "";
width: 100%;
height: 1px;
border-top: 1px solid #d8dce5;
position: absolute;
bottom: 0;
left: 0;
}
@{deep}(.hover-container) {
transition: background 0.2s;
height: 100%;
line-height: @navbarHeight + 5px;
padding: 0 5px;
text-align: center;
&:hover {
background: #f6f6f6;
}
}
.navbar__wrap--right {
display: flex;
align-items: center;
height: @navbarHeight;
position: absolute;
top: 0;
right: 20px;
@{deep}(.screenfull-container),
@{deep}(.user-container) {
line-height: @navbarHeight !important;
}
}
}
}
// content
.main__wrap--content {
height: 100%;
@{deep}(.el-scrollbar__wrap) {
overflow-x: hidden;
}
}
// content
}
.main__wrap--collapsed {
width: calc(~"100% - @{menuMinWidth}");
left: @menuMinWidth;
}
}
.layout-content-has-tags {
margin-top: @navbarHeight + @tagsViewHeight;
}
.has-tags {
position: fixed;
top: @navbarHeight;
left: @menuWidth;
width: calc(~"100% - @{menuWidth}");
transition: all 0.2s;
}
.ant-layout-header-collapsed,
.has-tags-collapsed {
left: 80px;
width: calc(~"100% - 80px");
//
.app__wrap--Classic {
.main__wrap--fixed--all {
margin-top: @navbarHeight + @tagsViewHeight !important;
height: calc(~"100% - @{navbarHeight} - @{tagsViewHeight}") !important;
}
.main__wrap--fixed--nav {
margin-top: @navbarHeight !important;
height: calc(~"100% - @{navbarHeight}") !important;
}
.main__wrap--fixed--tags {
margin-top: @tagsViewHeight !important;
height: calc(~"100% - @{tagsViewHeight}") !important;
}
.header__wrap--fixed {
position: fixed !important;
width: calc(~"100% - @{menuWidth}") !important;
top: 0 !important;
left: @menuWidth !important;
z-index: 200;
}
.header__wrap--collapsed {
width: calc(~"100% - @{menuMinWidth}") !important;
left: @menuMinWidth !important;
}
}
</style>

View File

@ -1,97 +1,157 @@
<template>
<a-layout>
<a-layout-header :class="'ant-layout-header--' + silderTheme">
<div :class="classObj" class="app__wrap">
<!-- Classic -->
<div
class="sidebar__wrap"
:class="{'sidebar__wrap--collapsed': collapsed}"
>
<logo
v-if="show_logo"
:theme="silderTheme"
v-if="showLogo && layout === 'Classic'"
:collapsed="collapsed"
/>
<silder
:theme="silderTheme"
mode="horizontal"
class="header-silder--wrap"
/>
<div class="right-menu">
<screenfull class="right-menu-item hover-effect screenfull-item" />
<sider :layout="layout" mode="vertical" />
</div>
<!-- Classic -->
<user-info class="right-menu-item hover-effect" />
</div>
</a-layout-header>
<a-layout class="layout-content">
<a-layout-sider
v-model:collapsed="collapsed"
:trigger="null"
collapsible
:class="'ant-layout-sider--' + theme"
class="sidebar-container-wrap"
>
<silder
<!-- Top -->
<div v-if="layout !== 'Classic'" class="sidebar__wrap--Top">
<div>
<logo
v-if="showLogo"
:collapsed="collapsed"
:theme="theme"
class="left-sider-wrap"
/>
</a-layout-sider>
<a-layout>
<div class="navbar-wrap">
<hamburger :collapsed="collapsed" class="hamburger-container" @toggleClick="setCollapsed" />
<breadcrumb class="breadcrumb-container" />
</div>
<div v-if="layout === 'Top'" class="sidebar__item--Top">
<sider :layout="layout" mode="horizontal" />
</div>
<div>
<div v-if="showScreenfull || showUserInfo" class="navbar__wrap--right">
<screenfull v-if="showScreenfull" class="hover-container screenfull-container" />
<user-info v-if="showUserInfo" class="hover-container user-container" />
</div>
<a-layout-content :class="{'layout-content-has-tags':has_tags}">
<scrollbar class="main-wrap">
<tags-view :class="{'has-tags':has_tags, 'has-tags-collapsed': collapsed && has_tags}" />
<app-main class="classic-module--wrap" />
</scrollbar>
</a-layout-content>
</a-layout>
</a-layout>
</a-layout>
</div>
</div>
<!-- Top -->
<div
class="main__wrap"
:class="{
'main__wrap--collapsed': collapsed
}"
>
<el-scrollbar
class="main__wrap--content"
:class="{
'main__wrap--fixed--all': fixedHeader && showNavbar && showTags,
'main__wrap--fixed--nav': fixedHeader && showNavbar && !showTags,
'main__wrap--fixed--tags': fixedHeader && !showNavbar && showTags
}"
>
<div
class="header__wrap"
:class="{
'header__wrap--fixed': fixedHeader,
'header__wrap--collapsed': fixedHeader && collapsed
}"
>
<div
v-if="showNavbar && layout !== 'Top'"
class="navbar__wrap"
>
<hamburger
v-if="showHamburger"
:collapsed="collapsed"
class="hover-container"
@toggleClick="setCollapsed"
/>
<breadcrumb v-if="showBreadcrumb" />
<!-- <div v-if="showScreenfull || showUserInfo" class="navbar__wrap--right">
<screenfull v-if="showScreenfull" class="hover-container screenfull-container" />
<user-info v-if="showUserInfo" class="hover-container user-container" />
</div> -->
</div>
<div
v-if="showTags"
class="tags__wrap"
>
<tags-view />
</div>
</div>
<app-main />
</el-scrollbar>
</div>
<!-- setting -->
<setting />
<!-- setting -->
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, computed } from 'vue'
import Logo from '../components/Logo.vue'
import Silder from '../components/Silder'
import UserInfo from '../components/UserInfo.vue'
import TagsView from '../components/TagsView.vue'
import { defineComponent, computed } from 'vue'
import { appStore } from '_p/index/store/modules/app'
import AppMain from '../components/AppMain.vue'
import Screenfull from '_c/Screenfull/index.vue'
import Scrollbar from '_c/Scrollbar/index.vue'
import TagsView from '_c/TagsView/index.vue'
import Logo from '_c/Logo/index.vue'
import Sider from '_c/Sider/index.vue'
import Hamburger from '_c/Hamburger/index.vue'
import Breadcrumb from '_c/Breadcrumb/index.vue'
import { appStore } from '_p/index/store/modules/app'
import config from '_p/index/config'
const { show_logo, has_tags, theme } = config
import Screenfull from '_c/Screenfull/index.vue'
import UserInfo from '_c/UserInfo/index.vue'
import Setting from '_c/Setting/index.vue'
export default defineComponent({
name: 'LeftTop',
components: {
Logo,
Silder,
UserInfo,
TagsView,
AppMain,
Screenfull,
Scrollbar,
Sider,
Hamburger,
Breadcrumb
},
props: {
theme: {
type: String as PropType<'light' | 'dark'>,
default: theme
},
silderTheme: {
type: String as PropType<'light' | 'dark'>,
default: 'light'
}
Breadcrumb,
Screenfull,
UserInfo,
AppMain,
TagsView,
Logo,
Setting
},
setup() {
const layout = computed(() => appStore.layout)
const collapsed = computed(() => appStore.collapsed)
const showLogo = computed(() => appStore.showLogo)
const showTags = computed(() => appStore.showTags)
const showBreadcrumb = computed(() => appStore.showBreadcrumb)
const showHamburger = computed(() => appStore.showHamburger)
const showScreenfull = computed(() => appStore.showScreenfull)
const showUserInfo = computed(() => appStore.showUserInfo)
const showNavbar = computed(() => appStore.showNavbar)
// const fixedNavbar = computed(() => appStore.fixedNavbar)
// const fixedTags = computed(() => appStore.fixedTags)
const fixedHeader = computed(() => appStore.fixedHeader)
const classObj = computed(() => {
const obj = {}
obj[`app__wrap--${layout.value}`] = true
return obj
})
function setCollapsed(collapsed: boolean): void {
appStore.SetCollapsed(collapsed)
}
return {
classObj,
layout,
collapsed,
show_logo,
has_tags,
showLogo,
showTags,
showBreadcrumb,
showHamburger,
showScreenfull,
showUserInfo,
showNavbar,
fixedHeader,
// fixedNavbar,
// fixedTags,
setCollapsed
}
}
@ -99,87 +159,173 @@ export default defineComponent({
</script>
<style lang="less" scoped>
@{deep}(.ant-layout-header) {
height: @topSilderHeight;
display: flex;
flex-direction: row;
justify-content: space-between;
position: fixed;
.app__wrap {
position: relative;
height: 100%;
width: 100%;
top: 0;
left: 0;
z-index: 20;
&--light {
background: @menuLightBg;
}
&--dark {
background: @menuBg;
.screenfull-item,
.name-item {
color: #fff;
}
}
.header-silder--wrap {
flex: 1;
margin: 0 50px;
height: 100% !important;
.ant-menu-horizontal {
height: @topSilderHeight;
line-height: @topSilderHeight;
border-bottom: 0;
}
.ant-menu-light {
height: calc(~"@{topSilderHeight} - 4px");
line-height: calc(~"@{topSilderHeight} - 2px");
}
}
.right-menu {
display: flex;
.screenfull-item {
line-height: @topSilderHeight;
}
.avatar-container {
margin-right: 0;
}
}
}
.ant-layout-sider--light {
background: @menuLightBg;
}
.ant-layout-sider--dark {
background: @menuBg;
}
.layout-content {
margin-top: @topSilderHeight;
height: calc(~"100vh - @{topSilderHeight}");
.navbar-wrap {
background: #fff;
padding: 0 20px;
border-top: 1px solid #f0f0f0;
height: @navbarHeight;
line-height: @navbarHeight;
display: flex;
}
.layout-content-has-tags {
margin-top: @tagsViewHeight;
.mian-wrap {
background-color: @contentBg;
}
}
.left-sider-wrap {
height: 100%;
}
.has-tags {
.sidebar__wrap {
position: fixed;
top: @topSilderHeight + @navbarHeight;
left: @menuWidth;
width: calc(~"100% - @{menuWidth}");
z-index: 20;
transition: all 0.2s;
width: @menuWidth;
top: 0;
left: 0;
height: 100%;
transition: width 0.2s;
}
.has-tags-collapsed {
left: 80px;
width: calc(~"100% - 80px");
.sidebar__wrap--collapsed {
width: @menuMinWidth;
@{deep}(.anticon-item) {
display: none;
}
}
.main__wrap {
position: absolute;
width: calc(~"100% - @{menuWidth}");
height: 100%;
top: 0;
left: @menuWidth;
transition: all 0.2s;
z-index: 1;
.header__wrap {
transition: all 0.2s;
.navbar__wrap {
display: flex;
align-items: center;
height: @navbarHeight;
padding: 0 20px 0 15px;
position: relative;
background: @contentBg;
&:after {
content: "";
width: 100%;
height: 1px;
border-top: 1px solid #d8dce5;
position: absolute;
bottom: 0;
left: 0;
}
@{deep}(.hover-container) {
transition: background 0.2s;
height: 100%;
line-height: @navbarHeight + 5px;
padding: 0 5px;
text-align: center;
&:hover {
background: #f6f6f6;
}
}
.navbar__wrap--right {
display: flex;
align-items: center;
height: @navbarHeight;
position: absolute;
top: 0;
right: 20px;
@{deep}(.screenfull-container),
@{deep}(.user-container) {
line-height: @navbarHeight !important;
}
}
}
}
// content
.main__wrap--content {
height: 100%;
@{deep}(.el-scrollbar__wrap) {
overflow-x: hidden;
}
}
// content
}
.main__wrap--collapsed {
width: calc(~"100% - @{menuMinWidth}");
left: @menuMinWidth;
}
}
// LeftTop
.app__wrap--LeftTop {
.main__wrap--fixed--all {
margin-top: @navbarHeight + @tagsViewHeight !important;
height: calc(~"100% - @{navbarHeight} - @{tagsViewHeight}") !important;
}
.main__wrap--fixed--nav {
margin-top: @navbarHeight !important;
height: calc(~"100% - @{navbarHeight}") !important;
}
.main__wrap--fixed--tags {
margin-top: @tagsViewHeight !important;
height: calc(~"100% - @{tagsViewHeight}") !important;
}
.header__wrap--fixed {
position: fixed !important;
width: calc(~"100% - @{menuWidth}") !important;
top: @topSiderHeight !important;
left: @menuWidth !important;
z-index: 200;
}
.header__wrap--collapsed {
width: calc(~"100% - @{menuMinWidth}") !important;
left: @menuMinWidth !important;
}
}
//
.app__wrap--LeftTop {
.sidebar__wrap--Top {
height: @topSiderHeight;
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 0 20px;
background-color: @topMenuBg;
position: relative;
&:after {
content: "";
width: 100%;
height: 1px;
border-top: 1px solid #d8dce5;
position: absolute;
bottom: 0;
left: 0;
}
.sidebar__item--Top {
flex: 1;
margin: 0 50px;
}
.navbar__wrap--right {
display: flex;
align-items: center;
height: @topSiderHeight;
@{deep}(.hover-container) {
transition: background 0.2s;
height: 100%;
line-height: @topSiderHeight;
padding: 0 5px;
text-align: center;
&:hover {
background: #f6f6f6;
}
}
}
}
}
.app__wrap--LeftTop {
.sidebar__wrap {
top: @topSiderHeight;
left: 0;
height: calc(~"100% - @{topSiderHeight}");
}
.main__wrap {
width: calc(~"100% - @{menuWidth}");
left: @menuWidth;
height: calc(~"100% - @{topSiderHeight}");
top: @topSiderHeight;
}
.main__wrap--collapsed {
width: calc(~"100% - @{menuMinWidth}");
left: @menuMinWidth;
}
}
</style>

View File

@ -1,128 +1,291 @@
<template>
<a-layout>
<a-layout-header :class="'ant-layout-header--' + theme">
<logo
v-if="show_logo"
:theme="theme"
/>
<silder
:theme="theme"
mode="horizontal"
class="header-silder--wrap"
/>
<div class="right-menu">
<screenfull class="right-menu-item hover-effect screenfull-item" />
<user-info class="right-menu-item hover-effect" />
<div :class="classObj" class="app__wrap">
<!-- Top -->
<div class="sidebar__wrap--Top">
<div>
<logo
v-if="showLogo"
:collapsed="collapsed"
/>
</div>
</a-layout-header>
<a-layout-content :class="{'layout-content-has-tags':has_tags}">
<scrollbar class="main-wrap">
<tags-view :class="{'has-tags':has_tags}" />
<app-main class="top-module--wrap" />
</scrollbar>
</a-layout-content>
</a-layout>
<div class="sidebar__item--Top">
<sider :layout="layout" mode="horizontal" />
</div>
<div>
<div v-if="showScreenfull || showUserInfo" class="navbar__wrap--right">
<screenfull v-if="showScreenfull" class="hover-container screenfull-container" />
<user-info v-if="showUserInfo" class="hover-container user-container" />
</div>
</div>
</div>
<!-- Top -->
<div
class="main__wrap"
:class="{
'main__wrap--collapsed': collapsed
}"
>
<el-scrollbar
class="main__wrap--content"
:class="{
'main__wrap--fixed--all': fixedHeader && showNavbar && showTags,
'main__wrap--fixed--nav': fixedHeader && showNavbar && !showTags,
'main__wrap--fixed--tags': fixedHeader && !showNavbar && showTags
}"
>
<div
class="header__wrap"
:class="{
'header__wrap--fixed': fixedHeader,
'header__wrap--collapsed': fixedHeader && collapsed
}"
>
<!-- <div
v-if="showNavbar && layout !== 'Top'"
class="navbar__wrap"
>
<hamburger
v-if="showHamburger"
:collapsed="collapsed"
class="hover-container"
@toggleClick="setCollapsed"
/>
<breadcrumb v-if="showBreadcrumb" />
<div v-if="showScreenfull || showUserInfo" class="navbar__wrap--right">
<screenfull v-if="showScreenfull" class="hover-container screenfull-container" />
<user-info v-if="showUserInfo" class="hover-container user-container" />
</div>
</div> -->
<div
v-if="showTags"
class="tags__wrap"
>
<tags-view />
</div>
</div>
<app-main />
</el-scrollbar>
</div>
<!-- setting -->
<setting />
<!-- setting -->
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue'
import Logo from '../components/Logo.vue'
import Silder from '../components/Silder'
import UserInfo from '../components/UserInfo.vue'
import TagsView from '../components/TagsView.vue'
import { defineComponent, computed } from 'vue'
import { appStore } from '_p/index/store/modules/app'
import AppMain from '../components/AppMain.vue'
import TagsView from '_c/TagsView/index.vue'
import Logo from '_c/Logo/index.vue'
import Sider from '_c/Sider/index.vue'
// import Hamburger from '_c/Hamburger/index.vue'
// import Breadcrumb from '_c/Breadcrumb/index.vue'
import Screenfull from '_c/Screenfull/index.vue'
import Scrollbar from '_c/Scrollbar/index.vue'
import config from '_p/index/config'
const { show_logo, has_tags, theme } = config
import UserInfo from '_c/UserInfo/index.vue'
import Setting from '_c/Setting/index.vue'
export default defineComponent({
name: 'Top',
components: {
Logo,
Silder,
UserInfo,
TagsView,
AppMain,
Sider,
// Hamburger,
// Breadcrumb,
Screenfull,
Scrollbar
},
props: {
theme: {
type: String as PropType<'light' | 'dark'>,
default: theme
}
UserInfo,
AppMain,
TagsView,
Logo,
Setting
},
setup() {
const layout = computed(() => appStore.layout)
const collapsed = computed(() => appStore.collapsed)
const showLogo = computed(() => appStore.showLogo)
const showTags = computed(() => appStore.showTags)
const showBreadcrumb = computed(() => appStore.showBreadcrumb)
const showHamburger = computed(() => appStore.showHamburger)
const showScreenfull = computed(() => appStore.showScreenfull)
const showUserInfo = computed(() => appStore.showUserInfo)
const showNavbar = computed(() => appStore.showNavbar)
// const fixedNavbar = computed(() => appStore.fixedNavbar)
// const fixedTags = computed(() => appStore.fixedTags)
const fixedHeader = computed(() => appStore.fixedHeader)
const classObj = computed(() => {
const obj = {}
obj[`app__wrap--${layout.value}`] = true
return obj
})
function setCollapsed(collapsed: boolean): void {
appStore.SetCollapsed(collapsed)
}
return {
show_logo,
has_tags
classObj,
layout,
collapsed,
showLogo,
showTags,
showBreadcrumb,
showHamburger,
showScreenfull,
showUserInfo,
showNavbar,
fixedHeader,
// fixedNavbar,
// fixedTags,
setCollapsed
}
}
})
</script>
<style lang="less" scoped>
@{deep}(.ant-layout-header) {
height: @topSilderHeight;
display: flex;
flex-direction: row;
justify-content: space-between;
position: fixed;
.app__wrap {
position: relative;
height: 100%;
width: 100%;
top: 0;
left: 0;
z-index: 20;
&--light {
background: @menuLightBg;
.sidebar__wrap {
position: fixed;
width: @menuWidth;
top: 0;
left: 0;
height: 100%;
transition: width 0.2s;
}
&--dark {
background: @menuBg;
.screenfull-item,
.name-item {
color: #fff;
.sidebar__wrap--collapsed {
width: @menuMinWidth;
@{deep}(.anticon-item) {
display: none;
}
}
.header-silder--wrap {
flex: 1;
margin: 0 50px;
height: 100% !important;
.ant-menu-horizontal {
height: @topSilderHeight;
line-height: @topSilderHeight;
border-bottom: 0;
.main__wrap {
position: absolute;
width: calc(~"100% - @{menuWidth}");
height: 100%;
top: 0;
left: @menuWidth;
transition: all 0.2s;
z-index: 1;
.header__wrap {
transition: all 0.2s;
.navbar__wrap {
display: flex;
align-items: center;
height: @navbarHeight;
padding: 0 20px 0 15px;
position: relative;
background: @contentBg;
&:after {
content: "";
width: 100%;
height: 1px;
border-top: 1px solid #d8dce5;
position: absolute;
bottom: 0;
left: 0;
}
@{deep}(.hover-container) {
transition: background 0.2s;
height: 100%;
line-height: @navbarHeight + 5px;
padding: 0 5px;
text-align: center;
&:hover {
background: #f6f6f6;
}
}
.navbar__wrap--right {
display: flex;
align-items: center;
height: @navbarHeight;
position: absolute;
top: 0;
right: 20px;
@{deep}(.screenfull-container),
@{deep}(.user-container) {
line-height: @navbarHeight !important;
}
}
}
}
.ant-menu-light {
height: calc(~"@{topSilderHeight} - 4px");
line-height: calc(~"@{topSilderHeight} - 4px");
// content
.main__wrap--content {
height: 100%;
@{deep}(.el-scrollbar__wrap) {
overflow-x: hidden;
}
}
// content
}
.right-menu {
.main__wrap--collapsed {
width: calc(~"100% - @{menuMinWidth}");
left: @menuMinWidth;
}
}
//
.app__wrap--Top {
.sidebar__wrap--Top {
height: @topSiderHeight;
display: flex;
.screenfull-item {
line-height: @topSilderHeight;
flex-direction: row;
justify-content: space-between;
padding: 0 20px;
background-color: @topMenuBg;
position: relative;
&:after {
content: "";
width: 100%;
height: 1px;
border-top: 1px solid #d8dce5;
position: absolute;
bottom: 0;
left: 0;
}
.avatar-container {
margin-right: 0;
.sidebar__item--Top {
flex: 1;
margin: 0 50px;
}
.navbar__wrap--right {
display: flex;
align-items: center;
height: @topSiderHeight;
@{deep}(.hover-container) {
transition: background 0.2s;
height: 100%;
line-height: @topSiderHeight;
padding: 0 5px;
text-align: center;
&:hover {
background: #f6f6f6;
}
}
}
}
}
.ant-layout-content {
margin-top: @topSilderHeight;
height: calc(~"100vh - @{topSilderHeight}");
.main-wrap {
background-color: @contentBg;
.header__wrap--fixed {
position: fixed !important;
width: 100% !important;
top: @topSiderHeight !important;
left: 0 !important;
z-index: 200;
}
.main__wrap {
width: 100%;
left: 0;
height: calc(~"100% - @{topSiderHeight}");
top: @topSiderHeight;
}
.main__wrap--fixed--all,
.main__wrap--fixed--tags {
margin-top: @navbarHeight !important;
height: calc(~"100% - @{navbarHeight}") !important;
}
}
.layout-content-has-tags {
margin-top: @topSilderHeight + @tagsViewHeight;
height: calc(~"100vh - @{topSilderHeight} - @{tagsViewHeight}");
}
.has-tags {
position: fixed;
top: @topSilderHeight;
left: 0;
width: 100%;
z-index: 20;
}
</style>

View File

@ -47,7 +47,7 @@ setupSvgIcon(app) // 全局注册svgIcon组件
setupGlobCom(app) // 注册全局公用组件
// setupDirectives(app) // 注册全局自定义指令
setupDirectives(app) // 注册全局自定义指令
// mockXHR() // mock注册

View File

@ -4,9 +4,7 @@ import NProgress from 'nprogress' // 引入进度条
import 'nprogress/nprogress.css' // 进度条样式
import config from './config'
const { user_info, title } = config
import { appStore } from '_p/index/store/modules/app'
import wsCache from '@/cache'
@ -20,7 +18,7 @@ import type { RouteRecordRaw } from 'vue-router'
const whiteList: string[] = ['/login'] // 不重定向白名单
router.beforeEach((to, from, next) => {
if (wsCache.get(user_info)) {
if (wsCache.get(appStore.userInfo)) {
if (to.path === '/login') {
next({ path: '/' })
} else {
@ -55,6 +53,6 @@ router.beforeEach((to, from, next) => {
})
router.afterEach((to) => {
document.title = getPageTitle(to.meta.title, title)
document.title = getPageTitle(to.meta.title, appStore.title)
NProgress.done() // 结束进度条
})

View File

@ -6,8 +6,6 @@ import { getParentLayout } from './utils'
/* Layout */
const Layout = () => import('../layout/index.vue')
/* ParentView */
import ParentView from '_c/ParentView/index.vue'
/**
* redirect: noredirect noredirect
@ -50,7 +48,8 @@ export const constantRouterMap: AppRouteRecordRaw[] = [
name: 'NoFind',
meta: {
hidden: true,
title: '404'
title: '404',
noTagsView: true
}
},
{
@ -83,281 +82,305 @@ export const constantRouterMap: AppRouteRecordRaw[] = [
}
]
},
// {
// path: '/external-link',
// component: Layout,
// meta: {},
// children: [
// {
// path: 'http://192.168.169.57/ue/2019/doc/vue-standard/dist/',
// meta: { title: '文档', icon: 'documentation' }
// }
// ]
// }
{
path: '/external-link',
component: Layout,
meta: {},
children: [
{
path: 'http://192.168.169.57/ue/2019/doc/vue-standard/dist/',
meta: { title: '文档', icon: 'documentation' }
}
]
}
]
export const asyncRouterMap: AppRouteRecordRaw[] = [
// {
// path: '/components-demo',
// component: Layout,
// redirect: '/components-demo/echarts',
// name: 'ComponentsDemo',
// meta: {
// title: '功能组件',
// icon: 'component',
// alwaysShow: true
// },
// children: [
// {
// path: 'echarts',
// component: () => import('_p/index/views/components-demo/echarts/index.vue'),
// name: 'EchartsDemo',
// meta: {
// title: '图表'
// }
// },
// {
// path: 'image',
// component: () => import('_p/index/views/components-demo/image/index.vue'),
// name: 'ImageDemo',
// meta: {
// title: '图片'
// }
// },
// {
// path: 'preview',
// component: () => import('_p/index/views/components-demo/preview/index.vue'),
// name: 'PreviewDemo',
// meta: {
// title: '图片预览'
// }
// },
// {
// path: 'scroll',
// component: () => import('_p/index/views/components-demo/scroll/index.vue'),
// name: 'ScrollDemo',
// meta: {
// title: '滚动'
// }
// },
// {
// path: 'count-to',
// component: () => import('_p/index/views/components-demo/count-to/index.vue'),
// name: 'CountToDemo',
// meta: {
// title: '数字动画'
// }
// },
// {
// path: 'search',
// component: () => import('_p/index/views/components-demo/search/index.vue'),
// name: 'SearchDemo',
// meta: {
// title: '查询'
// }
// },
// {
// path: 'button',
// component: () => import('_p/index/views/components-demo/button/index.vue'),
// name: 'ButtonDemo',
// meta: {
// title: '按钮'
// }
// },
// {
// path: 'editor',
// component: () => import('_p/index/views/components-demo/editor/index.vue'),
// name: 'EditorDemo',
// meta: {
// title: '富文本编辑器'
// }
// },
// {
// path: 'markdown',
// component: () => import('_p/index/views/components-demo/markdown/index.vue'),
// name: 'MarkdownDemo',
// meta: {
// title: 'markdown编辑器'
// }
// }
// ]
// },
// // {
// // path: '/table-demo',
// // component: Layout,
// // redirect: '/table-demo/basic-usage',
// // name: 'TableDemo',
// // meta: {
// // title: '表格',
// // icon: 'table',
// // alwaysShow: true
// // },
// // children: [
// // // {
// // // path: 'test',
// // // component: () => import('_p/index/views/table-demo/test'),
// // // name: 'test',
// // // meta: {
// // // title: 'test'
// // // }
// // // },
// // {
// // path: 'basic-usage',
// // component: () => import('_p/index/views/table-demo/basic-usage/index.vue'),
// // name: 'BasicUsage',
// // meta: {
// // title: '基础用法'
// // }
// // },
// // {
// // path: 'table-ellipsis',
// // component: () => import('_p/index/views/table-demo/table-ellipsis/index.vue'),
// // name: 'TableEllipsis',
// // meta: {
// // title: '单元格自动省略'
// // }
// // },
// // {
// // path: 'table-load',
// // component: () => import('_p/index/views/table-demo/table-load/index.vue'),
// // name: 'TableLoad',
// // meta: {
// // title: '远程加载数据'
// // }
// // },
// // {
// // path: 'table-border',
// // component: () => import('_p/index/views/table-demo/table-border/index.vue'),
// // name: 'TableBorder',
// // meta: {
// // title: '带边框'
// // }
// // },
// // {
// // path: 'table-merge',
// // component: () => import('_p/index/views/table-demo/table-merge/index.vue'),
// // name: 'TableMerge',
// // meta: {
// // title: '表格行/列合并'
// // }
// // },
// // {
// // path: 'custom-menu',
// // component: () => import('_p/index/views/table-demo/custom-menu/index.vue'),
// // name: 'CustomMenu',
// // meta: {
// // title: '自定义筛选菜单'
// // }
// // },
// // {
// // path: 'edit-cell',
// // component: () => import('_p/index/views/table-demo/edit-cell/index.vue'),
// // name: 'EditCell',
// // meta: {
// // title: '可编辑单元格'
// // }
// // },
// // {
// // path: 'edit-row',
// // component: () => import('_p/index/views/table-demo/edit-row/index.vue'),
// // name: 'EditRow',
// // meta: {
// // title: '可编辑行'
// // }
// // },
// // {
// // path: 'table-tree',
// // component: () => import('_p/index/views/table-demo/table-tree/index.vue'),
// // name: 'TableTree',
// // meta: {
// // title: '树形数据展示'
// // }
// // },
// // {
// // path: 'table-expanded',
// // component: () => import('_p/index/views/table-demo/table-expanded/index.vue'),
// // name: 'TableExpanded',
// // meta: {
// // title: '可展开'
// // }
// // },
// // {
// // path: 'fixed-header',
// // component: () => import('_p/index/views/table-demo/fixed-header/index.vue'),
// // name: 'FixedHeader',
// // meta: {
// // title: '固定头和列'
// // }
// // }
// // ]
// // },
// {
// path: '/directives-demo',
// component: Layout,
// redirect: '/directives-demo/clipboard',
// name: 'DirectivesDemo',
// meta: {
// title: '自定义指令',
// icon: 'clipboard',
// alwaysShow: true
// },
// children: [
// {
// path: 'clipboard',
// component: () => import('_p/index/views/directives-demo/clipboard/index.vue'),
// name: 'ClipboardDemo',
// meta: {
// title: 'Clipboard'
// }
// }
// ]
// },
// {
// path: '/hooks-demo',
// component: Layout,
// redirect: '/hooks-demo/watermark',
// name: 'HooksDemo',
// meta: {
// title: 'Hooks',
// icon: 'international',
// alwaysShow: true
// },
// children: [
// {
// path: 'watermark',
// component: () => import('_p/index/views/hooks-demo/useWatermark/index.vue'),
// name: 'UseWatermarkDemo',
// meta: {
// title: 'UseWaterMark'
// }
// },
// {
// path: 'useScrollTo',
// component: () => import('_p/index/views/hooks-demo/useScrollTo/index.vue'),
// name: 'UseScrollToDemo',
// meta: {
// title: 'UseScrollTo'
// }
// }
// ]
// },
// {
// path: '/icon',
// component: Layout,
// name: 'IconsDemo',
// meta: {},
// children: [
// {
// path: 'index',
// component: () => import('_p/index/views/icons/index.vue'),
// name: 'Icons',
// meta: {
// title: '图标',
// icon: 'icon'
// }
// }
// ]
// },
{
path: '/components-demo',
component: Layout,
redirect: '/components-demo/echarts',
name: 'ComponentsDemo',
meta: {
title: '功能组件',
icon: 'component',
alwaysShow: true
},
children: [
{
path: 'echarts',
component: () => import('_p/index/views/components-demo/echarts/index.vue'),
name: 'EchartsDemo',
meta: {
title: '图表'
}
},
{
path: 'preview',
component: () => import('_p/index/views/components-demo/preview/index.vue'),
name: 'PreviewDemo',
meta: {
title: '图片预览'
}
},
{
path: 'count-to',
component: () => import('_p/index/views/components-demo/count-to/index.vue'),
name: 'CountToDemo',
meta: {
title: '数字动画'
}
},
{
path: 'search',
component: () => import('_p/index/views/components-demo/search/index.vue'),
name: 'SearchDemo',
meta: {
title: '查询'
}
},
{
path: 'editor',
component: () => import('_p/index/views/components-demo/editor/index.vue'),
name: 'EditorDemo',
meta: {
title: '富文本编辑器'
}
},
{
path: 'markdown',
component: () => import('_p/index/views/components-demo/markdown/index.vue'),
name: 'MarkdownDemo',
meta: {
title: 'markdown编辑器'
}
}
]
},
{
path: '/table-demo',
component: Layout,
redirect: '/table-demo/basic-usage',
name: 'TableDemo',
meta: {
title: '表格',
icon: 'table',
alwaysShow: true
},
children: [
// {
// path: 'test',
// component: () => import('_p/index/views/table-demo/test'),
// name: 'test',
// meta: {
// title: 'test'
// }
// },
{
path: 'basic-table',
component: () => import('_p/index/views/table-demo/basic-table/index.vue'),
name: 'BasicTable',
meta: {
title: '基础表格'
}
},
{
path: 'stripe-table',
component: () => import('_p/index/views/table-demo/stripe-table/index.vue'),
name: 'StripeTable',
meta: {
title: '带斑马纹表格'
}
},
{
path: 'border-table',
component: () => import('_p/index/views/table-demo/border-table/index.vue'),
name: 'BorderTable',
meta: {
title: '带边框表格'
}
},
{
path: 'state-table',
component: () => import('_p/index/views/table-demo/state-table/index.vue'),
name: 'StateTable',
meta: {
title: '带状态表格'
}
},
{
path: 'fixed-header',
component: () => import('_p/index/views/table-demo/fixed-header/index.vue'),
name: 'FixedHeader',
meta: {
title: '固定表头'
}
},
{
path: 'fixed-column',
component: () => import('_p/index/views/table-demo/fixed-column/index.vue'),
name: 'FixedColumn',
meta: {
title: '固定列'
}
},
{
path: 'fixed-column-header',
component: () => import('_p/index/views/table-demo/fixed-column-header/index.vue'),
name: 'FixedColumnHeader',
meta: {
title: '固定列和表头'
}
},
{
path: 'fluid-height',
component: () => import('_p/index/views/table-demo/fluid-height/index.vue'),
name: 'FluidHeight',
meta: {
title: '流体高度'
}
},
{
path: 'multi-header',
component: () => import('_p/index/views/table-demo/multi-header/index.vue'),
name: 'MultiHeader',
meta: {
title: '多级表头'
}
},
{
path: 'single-choice',
component: () => import('_p/index/views/table-demo/single-choice/index.vue'),
name: 'SingleChoice',
meta: {
title: '单选'
}
},
{
path: 'multiple-choice',
component: () => import('_p/index/views/table-demo/multiple-choice/index.vue'),
name: 'MultipleChoice',
meta: {
title: '多选'
}
},
{
path: 'sort-table',
component: () => import('_p/index/views/table-demo/sort-table/index.vue'),
name: 'SortTable',
meta: {
title: '排序'
}
},
{
path: 'screen-table',
component: () => import('_p/index/views/table-demo/screen-table/index.vue'),
name: 'ScreenTable',
meta: {
title: '筛选'
}
},
{
path: 'expand-row',
component: () => import('_p/index/views/table-demo/expand-row/index.vue'),
name: 'ExpandRow',
meta: {
title: '展开行'
}
},
{
path: 'tree-and-load',
component: () => import('_p/index/views/table-demo/tree-and-load/index.vue'),
name: 'TreeAndLoad',
meta: {
title: '树形数据与懒加载'
}
},
{
path: 'custom-header',
component: () => import('_p/index/views/table-demo/custom-header/index.vue'),
name: 'CustomHeader',
meta: {
title: '自定义表头'
}
},
{
path: 'total-table',
component: () => import('_p/index/views/table-demo/total-table/index.vue'),
name: 'TotalTable',
meta: {
title: '表尾合计行'
}
},
]
},
{
path: '/directives-demo',
component: Layout,
redirect: '/directives-demo/clipboard',
name: 'DirectivesDemo',
meta: {
title: '自定义指令',
icon: 'clipboard',
alwaysShow: true
},
children: [
{
path: 'clipboard',
component: () => import('_p/index/views/directives-demo/clipboard/index.vue'),
name: 'ClipboardDemo',
meta: {
title: 'Clipboard'
}
}
]
},
{
path: '/hooks-demo',
component: Layout,
redirect: '/hooks-demo/watermark',
name: 'HooksDemo',
meta: {
title: 'Hooks',
icon: 'international',
alwaysShow: true
},
children: [
{
path: 'watermark',
component: () => import('_p/index/views/hooks-demo/useWatermark/index.vue'),
name: 'UseWatermarkDemo',
meta: {
title: 'UseWaterMark'
}
},
{
path: 'useScrollTo',
component: () => import('_p/index/views/hooks-demo/useScrollTo/index.vue'),
name: 'UseScrollToDemo',
meta: {
title: 'UseScrollTo'
}
}
]
},
{
path: '/icon',
component: Layout,
name: 'IconsDemo',
meta: {},
children: [
{
path: 'index',
component: () => import('_p/index/views/icons/index.vue'),
name: 'Icons',
meta: {
title: '图标',
icon: 'icon'
}
}
]
},
{
path: '/level',
component: Layout,

View File

@ -15,6 +15,8 @@ export interface AppState {
showScreenfull: Boolean
showUserInfo: Boolean
title: String
logoTitle: String
userInfo: String
}
@Module({ dynamic: true, namespaced: true, store, name: 'app' })
@ -31,7 +33,9 @@ class App extends VuexModule implements AppState {
public showHamburger = true // 是否显示侧边栏缩收按钮
public showScreenfull = true // 是否全屏按钮
public showUserInfo = true // 是否显示用户头像
public title = 'vue-antdv-admin' // 标题
public title = 'vue-element-plus-admin' // 标题
public logoTitle = 'vue-ElPlus-admin' // logo标题
public userInfo = 'userInfo' // 登录信息存储字段-建议每个项目换一个字段,避免与其他项目冲突
@Mutation
private SET_COLLAPSED(collapsed: boolean): void {
@ -85,6 +89,10 @@ class App extends VuexModule implements AppState {
private SET_TITLE(title: string): void {
this.title = title
}
@Mutation
private SET_LOGOTITLE(logoTitle: string): void {
this.logoTitle = logoTitle
}
@Action
public SetCollapsed(collapsed: boolean): void {
@ -138,6 +146,10 @@ class App extends VuexModule implements AppState {
public SetTitle(title: string): void {
this.SET_TITLE(title)
}
@Action
public SetLogoTitle(logoTitle: string): void {
this.SET_LOGOTITLE(logoTitle)
}
}
export const appStore = getModule<App>(App)

View File

@ -1,47 +1,47 @@
<template>
<div>
<a-alert message="扩展 antdv 的 Button 组件,实现 primary、success、warning、info、default 等主题样式。" style="margin-bottom: 20px;" />
<el-alert message="扩展 antdv 的 Button 组件,实现 primary、success、warning、info、default 等主题样式。" style="margin-bottom: 20px;" />
<div class="btn__wrap">default</div>
<div class="btn__item">
<a-button>默认按钮</a-button>
<a-button shape="round">默认按钮</a-button>
<a-button loading>默认按钮</a-button>
<a-button disabled>默认按钮</a-button>
<el-button>默认按钮</el-button>
<el-button shape="round">默认按钮</el-button>
<el-button loading>默认按钮</el-button>
<el-button disabled>默认按钮</el-button>
</div>
<div class="btn__wrap">primary</div>
<div class="btn__item">
<a-button type="primary">主要按钮</a-button>
<a-button type="primary" shape="round">主要按钮</a-button>
<a-button type="primary" loading>主要按钮</a-button>
<a-button type="primary" disabled>主要按钮</a-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="primary" shape="round">主要按钮</el-button>
<el-button type="primary" loading>主要按钮</el-button>
<el-button type="primary" disabled>主要按钮</el-button>
</div>
<div class="btn__wrap">success</div>
<div class="btn__item">
<a-button type="success">成功按钮</a-button>
<a-button type="success" shape="round">成功按钮</a-button>
<a-button type="success" loading>成功按钮</a-button>
<a-button type="success" disabled>成功按钮</a-button>
<el-button type="success">成功按钮</el-button>
<el-button type="success" shape="round">成功按钮</el-button>
<el-button type="success" loading>成功按钮</el-button>
<el-button type="success" disabled>成功按钮</el-button>
</div>
<div class="btn__wrap">warning</div>
<div class="btn__item">
<a-button type="warning">警告按钮</a-button>
<a-button type="warning" shape="round">警告按钮</a-button>
<a-button type="warning" loading>警告按钮</a-button>
<a-button type="warning" disabled>警告按钮</a-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="warning" shape="round">警告按钮</el-button>
<el-button type="warning" loading>警告按钮</el-button>
<el-button type="warning" disabled>警告按钮</el-button>
</div>
<div class="btn__wrap">danger</div>
<div class="btn__item">
<a-button type="danger">危险按钮</a-button>
<a-button type="danger" shape="round">危险按钮</a-button>
<a-button type="danger" loading>危险按钮</a-button>
<a-button type="danger" disabled>危险按钮</a-button>
<el-button type="danger">危险按钮</el-button>
<el-button type="danger" shape="round">危险按钮</el-button>
<el-button type="danger" loading>危险按钮</el-button>
<el-button type="danger" disabled>危险按钮</el-button>
</div>
<div class="btn__wrap">info</div>
<div class="btn__item">
<a-button type="info">信息按钮</a-button>
<a-button type="info" shape="round">信息按钮</a-button>
<a-button type="info" loading>信息按钮</a-button>
<a-button type="info" disabled>信息按钮</a-button>
<el-button type="info">信息按钮</el-button>
<el-button type="info" shape="round">信息按钮</el-button>
<el-button type="info" loading>信息按钮</el-button>
<el-button type="info" disabled>信息按钮</el-button>
</div>
</div>
</template>

View File

@ -1,6 +1,12 @@
<template>
<div>
<a-alert message="基于 vue-count-to 进行改造,支持所有 vue-count-to 参数。" style="margin-bottom: 20px;" />
<el-alert
effect="dark"
:closable="false"
title="基于 vue-count-to 进行改造,支持所有 vue-count-to 参数。"
type="info"
style="margin-bottom: 20px;"
/>
<div class="count-to">
<count-to
@ -17,44 +23,44 @@
/>
</div>
<div class="action">
<a-row :gutter="20">
<a-col :span="8">
<el-row :gutter="20">
<el-col :span="8">
<div class="action__item">
<span>startVal</span><a-input-number v-model:value="startVal" :min="0" />
<span>startVal</span><el-input-number v-model="startVal" :min="0" />
</div>
</a-col>
<a-col :span="8">
</el-col>
<el-col :span="8">
<div class="action__item">
<span>endVal</span><a-input-number v-model:value="endVal" :min="1" />
<span>endVal</span><el-input-number v-model="endVal" :min="1" />
</div>
</a-col>
<a-col :span="8">
</el-col>
<el-col :span="8">
<div class="action__item">
<span>duration</span><a-input-number v-model:value="duration" :min="1000" />
<span>duration</span><el-input-number v-model="duration" :min="1000" />
</div>
</a-col>
<a-col :span="8">
</el-col>
<el-col :span="8">
<div class="action__item">
<span>separator</span><a-input v-model:value="separator" />
<span>separator</span><el-input v-model="separator" />
</div>
</a-col>
<a-col :span="8">
</el-col>
<el-col :span="8">
<div class="action__item">
<span>prefix</span><a-input v-model:value="prefix" />
<span>prefix</span><el-input v-model="prefix" />
</div>
</a-col>
<a-col :span="8">
</el-col>
<el-col :span="8">
<div class="action__item">
<span>suffix</span><a-input v-model:value="suffix" />
<span>suffix</span><el-input v-model="suffix" />
</div>
</a-col>
<a-col :span="24">
</el-col>
<el-col :span="24">
<div style="text-align: center;margin-top: 20px;">
<a-button type="primary" @click="start">start</a-button>
<a-button style="margin-left: 10px;" @click="pauseResume">pause/resume</a-button>
<el-button type="primary" @click="start">start</el-button>
<el-button style="margin-left: 10px;" @click="pauseResume">pause/resume</el-button>
</div>
</a-col>
</a-row>
</el-col>
</el-row>
</div>
</div>
</template>
@ -121,10 +127,11 @@ export default defineComponent({
align-items: center;
margin-bottom: 10px;
&>span {
width: 100px;
display: inline-block;
width: 120px;
text-align: center;
}
@{deep}(.ant-input-number) {
@{deep}(.el-input-number) {
width: 100%;
}
}

View File

@ -1,29 +1,35 @@
<template>
<div>
<a-alert message="统一封装 Echart 组件,自适应宽度,只需传入 options 与 height 属性即可展示对应的图表。" type="info" style="margin-bottom: 20px;" />
<el-alert
effect="dark"
:closable="false"
title="统一封装 Echart 组件,自适应宽度,只需传入 options 与 height 属性即可展示对应的图表。"
type="info"
style="margin-bottom: 20px;"
/>
<a-row :gutter="20">
<a-col :span="10">
<el-row :gutter="20">
<el-col :span="10">
<div class="chart-wrap">
<echart :options="pieOptions" :height="'300px'" />
</div>
</a-col>
<a-col :span="14">
</el-col>
<el-col :span="14">
<div class="chart-wrap">
<echart :options="barOptions" :height="'300px'" />
</div>
</a-col>
<a-col :span="14">
</el-col>
<el-col :span="14">
<div class="chart-wrap">
<echart :options="lineOptions" :height="'300px'" />
</div>
</a-col>
<a-col :span="10">
</el-col>
<el-col :span="10">
<div class="chart-wrap">
<echart :options="pieOptions2" :height="'300px'" />
</div>
</a-col>
</a-row>
</el-col>
</el-row>
</div>
</template>

View File

@ -1,6 +1,12 @@
<template>
<div>
<a-alert message="基于 wangeditor 封装的 富文本 组件。" style="margin-bottom: 20px;" />
<el-alert
effect="dark"
:closable="false"
title="基于 wangeditor 封装的 富文本 组件。"
type="info"
style="margin-bottom: 20px;"
/>
<editor v-model:value="html" />
</div>
</template>

View File

@ -1,141 +0,0 @@
<template>
<div>
<a-alert message="抽取于 Element 的 Image 组件进行改造在保留原生img的特性下支持懒加载自定义占位、加载失败等。" style="margin-bottom: 20px;" />
<div class="demo-image">
<div v-for="fit in fits" :key="fit" class="block">
<span class="demonstration">{{ fit }}</span>
<c-image
:src="url"
:fit="fit"
style="width: 100px; height: 100px"
/>
</div>
</div>
<a-alert message="占位内容" style="margin-bottom: 20px;margin-top: 20px;" />
<div class="demo-image__placeholder">
<div class="block">
<span class="demonstration">默认</span>
<c-image :src="src" style="width: 300px; height: 200px" />
</div>
<div class="block">
<span class="demonstration">自定义</span>
<c-image :src="src" style="width: 300px; height: 200px">
<template #placeholder>
<div class="image-slot">
加载中...
</div>
</template>
</c-image>
</div>
</div>
<a-alert message="加载失败" style="margin-bottom: 20px;margin-top: 20px;" />
<div class="demo-image__error">
<div class="block">
<span class="demonstration">默认</span>
<c-image style="width: 300px; height: 200px" />
</div>
<div class="block">
<span class="demonstration">自定义</span>
<c-image style="width: 300px; height: 200px">
<template #error>
<div class="image-slot">
<PictureOutlined />
</div>
</template>
</c-image>
</div>
</div>
<a-alert message="懒加载" style="margin-bottom: 20px;margin-top: 20px;" />
<div class="demo-image__lazy">
<c-image
v-for="url in urls"
:key="url"
:src="url"
lazy
style="display: block;min-height: 200px;margin-bottom: 10px;"
/>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import CImage from '_c/Image/index.vue'
import { PictureOutlined } from '@ant-design/icons-vue'
export default defineComponent({
// name: 'Image',
components: {
CImage,
PictureOutlined
},
setup() {
const fits = ref<string[]>(['fill', 'contain', 'cover', 'none', 'scale-down'])
const url = ref<string>('https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg')
const src = ref<string>('https://cube.elemecdn.com/6/94/4d3ea53c084bad6931a56d5158a48jpeg.jpeg')
const urls = ref<string[]>([
'https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg',
'https://fuss10.elemecdn.com/9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg',
'https://fuss10.elemecdn.com/d/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg',
'https://fuss10.elemecdn.com/3/28/bbf893f792f03a54408b3b7a7ebf0jpeg.jpeg',
'https://fuss10.elemecdn.com/2/11/6535bcfb26e4c79b48ddde44f4b6fjpeg.jpeg'
])
return {
fits,
url,
src,
urls
}
}
})
</script>
<style lang="less" scoped>
.demo-image,
.demo-image__placeholder,
.demo-image__error {
background: #fff;
}
.demo-image .block,
.demo-image__error .block,
.demo-image__placeholder .block {
padding: 30px 0;
text-align: center;
border-right: 1px solid #eff2f6;
display: inline-block;
width: 20%;
box-sizing: border-box;
vertical-align: top;
.demonstration {
display: block;
margin-bottom: 5px;
}
.image__error,
.image-slot {
display: flex;
justify-content: center;
align-items: center;
font-size: 14px;
color: #c0c4cc;
vertical-align: middle;
height: 100%;
height: 100%;
background: #f5f7fa;
}
}
.demo-image__error .block,
.demo-image__placeholder .block {
text-align: center;
width: 49%;
}
.demo-image__lazy {
width: 800px;
height: 400px;
overflow-y: auto;
}
</style>

View File

@ -1,6 +1,12 @@
<template>
<div>
<a-alert message="基于 vditor 封装的 Markdown编辑器 组件。" style="margin-bottom: 20px;" />
<el-alert
effect="dark"
:closable="false"
title="基于 vditor 封装的 Markdown编辑器 组件。"
type="info"
style="margin-bottom: 20px;"
/>
<markdown v-model:value="html" />
</div>
</template>

View File

@ -1,8 +1,20 @@
<template>
<div>
<a-alert message="抽取于 Element 的图片预览组件进行改造,实现函数式调用组件,无需基于图片进行点击预览。" style="margin-bottom: 20px;" />
<el-alert
effect="dark"
:closable="false"
title="抽取于 Element 的图片预览组件进行改造,实现函数式调用组件,无需基于图片进行点击预览。"
type="info"
style="margin-bottom: 20px;"
/>
<a-alert message="有底图预览" style="margin-bottom: 20px;" />
<el-alert
effect="dark"
:closable="false"
title="有底图预览。"
type="info"
style="margin-bottom: 20px;"
/>
<div class="img-wrap">
<div
v-for="(item, $index) in imgList"
@ -14,18 +26,30 @@
</div>
</div>
<a-alert message="无底图预览" style="margin-bottom: 20px; margin-top: 20px;" />
<a-button type="primary" @click="showNoImg">点击预览</a-button>
<el-alert
effect="dark"
:closable="false"
title="无底图预览。"
type="info"
style="margin-bottom: 20px; margin-top: 20px;"
/>
<el-button type="primary" @click="showNoImg">点击预览</el-button>
<a-alert message="点击事件,包含图片点击事件以及关闭事件。" style="margin-bottom: 20px; margin-top: 20px;" />
<a-button type="primary" @click="showImg">点击预览</a-button>
<el-alert
effect="dark"
:closable="false"
title="点击事件,包含图片点击事件以及关闭事件。。"
type="info"
style="margin-bottom: 20px; margin-top: 20px;"
/>
<el-button type="primary" @click="showImg">点击预览</el-button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import { createImgPreview } from '_c/Preview/functional'
import { message } from 'ant-design-vue'
import { ElMessage } from 'element-plus'
export default defineComponent({
// name: 'PreviewDemo',
setup() {
@ -59,10 +83,10 @@ export default defineComponent({
show: true,
index: 0,
onSelect: (i: number) => {
message.info('当前点击的图片索引:' + i)
ElMessage.info('当前点击的图片索引:' + i)
},
onClose: (i: number) => {
message.info('关闭之后的图片索引:' + i)
ElMessage.info('关闭之后的图片索引:' + i)
}
})
}

View File

@ -1,8 +1,8 @@
<template>
<div>
<a-alert message="抽取于 Element 的 Scrollbar 组件进行改造统一美化各个浏览器滚动条,保持一致性。" style="margin-bottom: 20px;" />
<el-alert message="抽取于 Element 的 Scrollbar 组件进行改造统一美化各个浏览器滚动条,保持一致性。" style="margin-bottom: 20px;" />
<a-alert message="横向滚动,外围容器需要设置固定宽度。" style="margin-bottom: 20px;" />
<el-alert message="横向滚动,外围容器需要设置固定宽度。" style="margin-bottom: 20px;" />
<div class="deom__wrap deom__wrap--horizontal">
<scrollbar>
<ul class="deom-ul__wrap">
@ -11,7 +11,7 @@
</scrollbar>
</div>
<a-alert message="纵向滚动,外围容器需要设置固定高度。" style="margin-bottom: 20px;margin-top: 20px;" />
<el-alert message="纵向滚动,外围容器需要设置固定高度。" style="margin-bottom: 20px;margin-top: 20px;" />
<div class="deom__wrap deom__wrap--vertical">
<scrollbar>
<ul class="deom-ul__wrap">

View File

@ -2,16 +2,16 @@ export const classicData = [
{
label: '即时配送',
value: true,
type: 'switch',
itemType: 'switch',
field: 'delivery'
},
{
label: '活动名称',
value: '',
type: 'input',
placeholder: '活动名称',
itemType: 'input',
field: 'name',
allowClear: true,
placeholder: '活动名称',
clearable: true,
rules: [
{
required: true,
@ -21,11 +21,11 @@ export const classicData = [
},
{
label: '活动区域',
value: undefined,
type: 'select',
value: '',
itemType: 'select',
placeholder: '活动区域',
clearable: true,
field: 'region',
allowClear: true,
options: [
{
title: '区域一',
@ -38,7 +38,7 @@ export const classicData = [
],
rules: [
{
type: 'string',
itemType: 'string',
required: true,
message: '请选择活动区域'
}
@ -47,7 +47,7 @@ export const classicData = [
{
label: '特殊资源',
value: '2',
type: 'radio',
itemType: 'radio',
field: 'resource',
radioType: 'button', // button or radio
options: [
@ -61,87 +61,89 @@ export const classicData = [
}
]
},
{
label: '组织机构',
value: [],
type: 'treeSelect',
field: 'company',
allowClear: true,
placeholder: '请选择组织机构',
treeCheckable: false,
maxTagCount: 2,
options: [
{
title: 'Node1',
value: '0-0',
key: '0-0',
children: [
{
title: 'Child Node1',
value: '0-0-0',
key: '0-0-0'
}
]
},
{
title: 'Node2',
value: '0-1',
key: '0-1',
children: [
{
title: 'Child Node3',
value: '0-1-0',
key: '0-1-0',
disabled: true
},
{
title: 'Child Node4',
value: '0-1-1',
key: '0-1-1'
},
{
title: 'Child Node5',
value: '0-1-2',
key: '0-1-2'
}
]
}
]
},
// {
// label: '组织机构',
// value: [],
// itemType: 'treeSelect',
// field: 'company',
// allowClear: true,
// placeholder: '请选择组织机构',
// treeCheckable: false,
// maxTagCount: 2,
// options: [
// {
// title: 'Node1',
// value: '0-0',
// key: '0-0',
// children: [
// {
// title: 'Child Node1',
// value: '0-0-0',
// key: '0-0-0'
// }
// ]
// },
// {
// title: 'Node2',
// value: '0-1',
// key: '0-1',
// children: [
// {
// title: 'Child Node3',
// value: '0-1-0',
// key: '0-1-0',
// disabled: true
// },
// {
// title: 'Child Node4',
// value: '0-1-1',
// key: '0-1-1'
// },
// {
// title: 'Child Node5',
// value: '0-1-2',
// key: '0-1-2'
// }
// ]
// }
// ]
// },
{
label: '日选择器',
value: null,
type: 'datePicker',
value: '',
itemType: 'datePicker',
field: 'date1',
allowClear: true,
valueFormat: 'YYYY-MM-DD',
clearable: true,
format: 'YYYY-MM-DD',
placeholder: '请选择日期'
},
{
label: '月选择器',
value: null,
type: 'monthPicker',
value: '',
itemType: 'datePicker',
field: 'date2',
valueFormat: 'YYYY-MM',
allowClear: true,
clearable: true,
format: 'YYYY-MM',
placeholder: '请选择日期'
},
{
label: '范围选择器',
value: null,
type: 'rangePicker',
valueFormat: 'YYYY-MM-DD',
value: [],
itemType: 'datePicker',
field: 'date3',
allowClear: true,
placeholder: ['请选择日期', '请选择日期']
clearable: true,
type: 'daterange',
rangeSeparator: '至',
startPlaceholder: '开始日期',
endPlaceholder: '结束日期'
},
{
label: '周选择器',
value: null,
type: 'weekPicker',
value: '',
itemType: 'datePicker',
field: 'date4',
valueFormat: 'YYYY-MM-DD',
allowClear: true,
type: 'week',
clearable: true,
placeholder: '请选择日期'
}
]

View File

@ -1,7 +1,19 @@
<template>
<div>
<a-alert message="封装 antdv 的 Form 组件,实现查询、重置等功能,并提供了三种布局风格。" style="margin-bottom: 20px;" />
<a-alert message="经典风格" style="margin-bottom: 20px;margin-top: 20px;" />
<el-alert
effect="dark"
:closable="false"
title="封装 Element 的 Form 组件,实现查询、重置等功能,并提供了三种布局风格。"
type="info"
style="margin-bottom: 20px;"
/>
<el-alert
effect="dark"
:closable="false"
title="经典风格。"
type="info"
style="margin-bottom: 20px;margin-top: 20px;"
/>
<div class="searh">
<search
:data="classicData"
@ -13,7 +25,13 @@
</div>
</div>
<a-alert message="底部操作按钮风格" style="margin-bottom: 20px;margin-top: 20px;" />
<el-alert
effect="dark"
:closable="false"
title="底部操作按钮风格。"
type="info"
style="margin-bottom: 20px;margin-top: 20px;"
/>
<div class="searh">
<search
layout="bottom"
@ -26,7 +44,13 @@
</div>
</div>
<a-alert message="右侧操作按钮风格" style="margin-bottom: 20px;margin-top: 20px;" />
<el-alert
effect="dark"
:closable="false"
title="右侧操作按钮风格。"
type="info"
style="margin-bottom: 20px;margin-top: 20px;"
/>
<div class="searh">
<search
layout="right"

View File

@ -16,70 +16,6 @@
<div class="chart__wrap">
<echart :options="lineEchatOptions" :height="'300px'" />
</div>
<panel-group />
<el-row :gutter="20">
<el-col :span="10">
<div class="chart__wrap">
<echart :options="pieEchatOptions" :height="'300px'" />
</div>
</el-col>
<el-col :span="14">
<div class="chart__wrap">
<echart :options="barEchatOptions" :height="'300px'" />
</div>
</el-col>
</el-row>
<div class="chart__wrap">
<echart :options="lineEchatOptions" :height="'300px'" />
</div>
<panel-group />
<el-row :gutter="20">
<el-col :span="10">
<div class="chart__wrap">
<echart :options="pieEchatOptions" :height="'300px'" />
</div>
</el-col>
<el-col :span="14">
<div class="chart__wrap">
<echart :options="barEchatOptions" :height="'300px'" />
</div>
</el-col>
</el-row>
<div class="chart__wrap">
<echart :options="lineEchatOptions" :height="'300px'" />
</div>
<panel-group />
<el-row :gutter="20">
<el-col :span="10">
<div class="chart__wrap">
<echart :options="pieEchatOptions" :height="'300px'" />
</div>
</el-col>
<el-col :span="14">
<div class="chart__wrap">
<echart :options="barEchatOptions" :height="'300px'" />
</div>
</el-col>
</el-row>
<div class="chart__wrap">
<echart :options="lineEchatOptions" :height="'300px'" />
</div>
<panel-group />
<el-row :gutter="20">
<el-col :span="10">
<div class="chart__wrap">
<echart :options="pieEchatOptions" :height="'300px'" />
</div>
</el-col>
<el-col :span="14">
<div class="chart__wrap">
<echart :options="barEchatOptions" :height="'300px'" />
</div>
</el-col>
</el-row>
<div class="chart__wrap">
<echart :options="lineEchatOptions" :height="'300px'" />
</div>
</div>
</template>

View File

@ -1,38 +1,56 @@
<template>
<div>
<a-alert message="自定义指令v-clipboard用于复制文本。" style="margin-bottom: 20px;" />
<a-alert message="基础示例" style="margin-bottom: 20px;margin-top: 20px;" />
<el-alert
effect="dark"
:closable="false"
title="自定义指令v-clipboard用于复制文本。"
type="info"
style="margin-bottom: 20px;"
/>
<el-alert
effect="dark"
:closable="false"
title="基础示例。"
type="info"
style="margin-bottom: 20px;margin-top: 20px;"
/>
<div class="input__wrap">
<a-input v-model:value="inputVal1" placeholder="请输入要复制的文本" />
<a-button v-clipboard="inputVal1" type="primary">复制</a-button>
<el-input v-model="inputVal1" placeholder="请输入要复制的文本" />
<el-button v-clipboard="inputVal1" type="primary">复制</el-button>
</div>
<a-alert message="自定义回调方法" style="margin-bottom: 20px;margin-top: 20px;" />
<el-alert
effect="dark"
:closable="false"
title="自定义回调方法。"
type="info"
style="margin-bottom: 20px;margin-top: 20px;"
/>
<div class="input__wrap">
<a-input v-model:value="inputVal2" placeholder="请输入要复制的文本" />
<a-button
<el-input v-model="inputVal2" placeholder="请输入要复制的文本" />
<el-button
v-clipboard="inputVal2"
v-clipboard:success="clipboardSuccess"
v-clipboard:error="clipboardSuccess"
type="primary"
>复制</a-button>
>复制</el-button>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import { message } from 'ant-design-vue'
import { ElMessage } from 'element-plus'
export default defineComponent({
// name: 'Clipboard'
setup() {
const inputVal1 = ref<string>('')
const inputVal2 = ref<string>('')
function clipboardSuccess(val: any) {
message.success('我是自定义成功回调:' + val.text)
ElMessage.success('我是自定义成功回调:' + val.text)
}
function clipboardError() {
message.error('我是自定义失败回调')
ElMessage.error('我是自定义失败回调')
}
return {
inputVal1, inputVal2,

View File

@ -1,18 +1,24 @@
<template>
<div>
<a-alert message="useScrollTo提供JS滚动过渡动画功能。" style="margin-bottom: 20px;" />
<el-alert
effect="dark"
:closable="false"
title="useScrollTo提供JS滚动过渡动画功能。"
type="info"
style="margin-bottom: 20px;"
/>
<div class="button__wrap">
<a-button type="primary" @click="scrollTo(100, 'scrollTop')">垂直滚动100px</a-button>
<a-button type="primary" @click="scrollTo(800, 'scrollTop')">垂直滚动800px</a-button>
<a-button type="primary" @click="scrollTo(100, 'scrollLeft')">水平滚动100px</a-button>
<a-button type="primary" @click="scrollTo(500, 'scrollLeft')">水平滚动500px</a-button>
<el-button type="primary" @click="scrollTo(100, 'scrollTop')">垂直滚动100px</el-button>
<el-button type="primary" @click="scrollTo(800, 'scrollTop')">垂直滚动800px</el-button>
<el-button type="primary" @click="scrollTo(100, 'scrollLeft')">水平滚动100px</el-button>
<el-button type="primary" @click="scrollTo(500, 'scrollLeft')">水平滚动500px</el-button>
</div>
<div class="deom__wrap">
<scrollbar ref="scrollContainer">
<el-scrollbar ref="scrollContainer">
<ul class="deom-ul__wrap">
<li v-for="i in 100" :key="i">{{ i }}</li>
</ul>
</scrollbar>
</el-scrollbar>
</div>
</div>
</template>
@ -20,17 +26,13 @@
<script lang="ts">
import { defineComponent, ref, unref } from 'vue'
import { useScrollTo } from '@/hooks/useScrollTo'
import Scrollbar from '_c/Scrollbar/index.vue'
export default defineComponent({
// name: 'UseScrollToDemo',
components: {
Scrollbar
},
setup() {
const scrollContainer = ref<HTMLElement | null>(null)
function scrollTo(to: number, position: string): void {
const $scrollWrapper: any = (unref(scrollContainer) as any).$.wrap
const $scrollWrapper: any = (unref(scrollContainer) as any).wrap
const { start } = useScrollTo({
el: $scrollWrapper,
position: position,

View File

@ -1,9 +1,15 @@
<template>
<div>
<a-alert message="useWatermark为整个系统提供水印功能。" style="margin-bottom: 20px;" />
<a-button type="primary" @click="setWatermark('vue-antdv-admin')">创建水印</a-button>
<a-button type="danger" @click="clear">清除水印</a-button>
<a-button type="warning" @click="setWatermark('vue-antdv-admin-new')">重置水印</a-button>
<el-alert
effect="dark"
:closable="false"
title="useWatermark为整个系统提供水印功能。"
type="info"
style="margin-bottom: 20px;"
/>
<el-button type="primary" @click="setWatermark('vue-antdv-admin')">创建水印</el-button>
<el-button type="danger" @click="clear">清除水印</el-button>
<el-button type="warning" @click="setWatermark('vue-antdv-admin-new')">重置水印</el-button>
</div>
</template>

View File

@ -5,15 +5,12 @@
:key="item"
v-clipboard="generateIconCode(item)"
>
<a-tooltip placement="top">
<template #title>
{{ generateIconCode(item) }}
</template>
<el-tooltip placement="top" :content="generateIconCode(item)">
<div class="icon-item">
<svg-icon :icon-class="item" class-name="disabled" />
<span>{{ item }}</span>
</div>
</a-tooltip>
</el-tooltip>
</div>
</div>
</template>

View File

@ -61,7 +61,7 @@ import { defineComponent, ref, unref, reactive, watch } from 'vue'
import { useRouter } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'
import { permissionStore } from '_p/index/store/modules/permission'
import config from '_p/index/config'
import { appStore } from '_p/index/store/modules/app'
import wsCache from '@/cache'
interface FormModule {
@ -106,7 +106,7 @@ export default defineComponent({
permissionStore.addRouters.forEach(async(route: RouteRecordRaw) => {
await addRoute(route.name!, route) // 访
})
wsCache.set(config.user_info, form)
wsCache.set(appStore.userInfo, form)
permissionStore.SetIsAddRouters(true)
push({ path: redirect.value || '/' })
})

View File

@ -0,0 +1,73 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="基于 Element 的 Table 组件进行二次封装,实现数据驱动,支持所有 Table 参数 -- 基础表格"
type="info"
style="margin-bottom: 20px;"
/>
<com-table v-loading="loading" :columns="columns" :data="tableData" />
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import ComTable from '_c/Table/index.vue'
const columns = [
{
key: 'date',
label: '日期'
},
{
key: 'name',
label: '姓名'
},
{
key: 'address',
label: '地址'
}
]
const tableData = [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}
]
export default defineComponent({
// name: 'BasicTable',
components: {
ComTable
},
setup() {
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 2000)
return {
columns,
tableData,
loading
}
}
})
</script>
<style>
</style>

View File

@ -1,118 +0,0 @@
<template>
<div>
<com-table
:loading="loading"
:columns="columns"
:data-source="data"
>
<template #name="{ text }">
<a>{{ text }}</a>
</template>
<template #customTitle>
<span><smile-outlined /> Name</span>
</template>
<template #tags="{ text: tags }">
<span>
<a-tag
v-for="tag in tags"
:key="tag"
:color="tag === 'loser' ? 'volcano' : tag.length > 5 ? 'geekblue' : 'green'"
>
{{ tag.toUpperCase() }}
</a-tag>
</span>
</template>
<template #action="{ record }">
<span>
<a>Invite -- {{ record.name }}</a>
<a-divider type="vertical" />
<a>Delete</a>
<a-divider type="vertical" />
<a class="ant-dropdown-link"> More actions <down-outlined /> </a>
</span>
</template>
</com-table>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import ComTable from '_c/Table'
import { SmileOutlined, DownOutlined } from '@ant-design/icons-vue'
const columns = [
{
dataIndex: 'name',
key: 'name',
slots: { title: 'customTitle', customRender: 'name' }
},
{
title: 'Age',
dataIndex: 'age',
key: 'age'
},
{
title: 'Address',
dataIndex: 'address',
key: 'address'
},
{
title: 'Tags',
key: 'tags',
dataIndex: 'tags',
slots: { customRender: 'tags' }
},
{
title: 'Action',
key: 'action',
slots: { customRender: 'action' }
}
]
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
tags: ['nice', 'developer']
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
tags: ['loser']
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
tags: ['cool', 'teacher']
}
]
export default defineComponent({
// name: 'BasicUsage',
components: {
ComTable,
SmileOutlined,
DownOutlined
},
setup() {
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 1000)
return {
loading,
columns,
data
}
}
})
</script>
<style>
</style>

View File

@ -0,0 +1,78 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="基于 Element 的 Table 组件进行二次封装,实现数据驱动,支持所有 Table 参数 -- 带边框表格"
type="info"
style="margin-bottom: 20px;"
/>
<com-table
v-loading="loading"
:columns="columns"
:data="tableData"
border
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import ComTable from '_c/Table/index.vue'
const columns = [
{
key: 'date',
label: '日期'
},
{
key: 'name',
label: '姓名'
},
{
key: 'address',
label: '地址'
}
]
const tableData = [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}
]
export default defineComponent({
// name: 'BorderTable',
components: {
ComTable
},
setup() {
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 2000)
return {
columns,
tableData,
loading
}
}
})
</script>
<style>
</style>

View File

@ -0,0 +1,111 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="基于 Element 的 Table 组件进行二次封装,实现数据驱动,支持所有 Table 参数 -- 自定义表头"
type="info"
style="margin-bottom: 20px;"
/>
<com-table
v-loading="loading"
:columns="columns"
:data="tableData.filter(data => !search || data.name.toLowerCase().includes(search.toLowerCase()))"
>
<template #actionHeader>
<el-input
v-model="search"
size="mini"
placeholder="输入关键字搜索"
/>
</template>
<template #action="scope">
<el-button
size="mini"
@click="handleEdit(scope.$index, scope.row)"
>Edit</el-button>
<el-button
size="mini"
type="danger"
@click="handleDelete(scope.$index, scope.row)"
>Delete</el-button>
</template>
</com-table>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import ComTable from '_c/Table/index.vue'
const columns = [
{
key: 'date',
label: '日期'
},
{
key: 'name',
label: '姓名'
},
{
key: 'action',
slots: {
header: 'actionHeader',
default: 'action'
}
}
]
const tableData = [
{
date: '2016-05-02',
name: '王小虎1',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-04',
name: '王小虎2',
address: '上海市普陀区金沙江路 1517 弄'
}, {
date: '2016-05-01',
name: '王小虎3',
address: '上海市普陀区金沙江路 1519 弄'
}, {
date: '2016-05-03',
name: '王小虎4',
address: '上海市普陀区金沙江路 1516 弄'
}
]
export default defineComponent({
// name: 'CustomHeader',
components: {
ComTable
},
setup() {
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 2000)
const search = ref<string>('')
function handleEdit(index: number, row: any) {
console.log(index, row)
}
function handleDelete(index: number, row: any) {
console.log(index, row)
}
return {
columns,
tableData,
loading,
search,
handleEdit, handleDelete
}
}
})
</script>
<style>
</style>

View File

@ -1,198 +0,0 @@
<template>
<div>
<com-table :data-source="data" :columns="columns">
<template #filterDropdown="{ setSelectedKeys, selectedKeys, confirm, clearFilters, column }">
<div style="padding: 8px">
<a-input
:ref="c => (searchInput = c)"
v-model:value="selectedKeys[0]"
:placeholder="`Search ${column.dataIndex}`"
style="width: 188px; margin-bottom: 8px; display: block;"
@change="e => setSelectedKeys(e.target.value ? [e.target.value] : [])"
@pressEnter="handleSearch(selectedKeys, confirm, column.dataIndex)"
/>
<a-button
type="primary"
size="small"
style="width: 90px; margin-right: 8px"
@click="handleSearch(selectedKeys, confirm, column.dataIndex)"
>
<template #icon><SearchOutlined /></template>
Search
</a-button>
<a-button size="small" style="width: 90px" @click="handleReset(clearFilters)">
Reset
</a-button>
</div>
</template>
<template #filterIcon="filtered">
<search-outlined :style="{ color: filtered ? '#108ee9' : undefined }" />
</template>
<template #customRender="{ text, column }">
<span v-if="searchText && searchedColumn === column.dataIndex">
<template
v-for="(fragment, i) in text
.toString()
.split(new RegExp(`(?<=${searchText})|(?=${searchText})`, 'i'))"
:key="i"
>
<mark v-if="fragment.toLowerCase() === searchText.toLowerCase()" class="highlight">
{{ fragment }}
</mark>
<template v-else>{{ fragment }}</template>
</template>
</span>
<template v-else>
{{ text }}
</template>
</template>
</com-table>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, getCurrentInstance } from 'vue'
import ComTable from '_c/Table'
import { SearchOutlined } from '@ant-design/icons-vue'
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park'
},
{
key: '2',
name: 'Joe Black',
age: 42,
address: 'London No. 1 Lake Park'
},
{
key: '3',
name: 'Jim Green',
age: 32,
address: 'Sidney No. 1 Lake Park'
},
{
key: '4',
name: 'Jim Red',
age: 32,
address: 'London No. 2 Lake Park'
}
]
export default defineComponent({
// name: 'CustomMenu',
components: {
ComTable,
SearchOutlined
},
setup() {
const { ctx } = getCurrentInstance() as any
const searchText = ref<string>('')
const searchInput = ref<any>(null)
const searchedColumn = ref<string>('')
const columns = ref<any[]>([
{
title: 'Name',
dataIndex: 'name',
key: 'name',
slots: {
filterDropdown: 'filterDropdown',
filterIcon: 'filterIcon',
customRender: 'customRender'
},
onFilter: (value: string, record: any) =>
record.name
.toString()
.toLowerCase()
.includes(value.toLowerCase()),
onFilterDropdownVisibleChange: (visible: boolean) => {
if (visible) {
setTimeout(() => {
console.log(searchInput.value)
searchInput.value.focus()
}, 0)
}
}
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
slots: {
filterDropdown: 'filterDropdown',
filterIcon: 'filterIcon',
customRender: 'customRender'
},
onFilter: (value: string, record: any) =>
record.age
.toString()
.toLowerCase()
.includes(value.toLowerCase()),
onFilterDropdownVisibleChange: (visible: boolean) => {
if (visible) {
setTimeout(() => {
searchInput.value.focus()
})
}
}
},
{
title: 'Address',
dataIndex: 'address',
key: 'address',
slots: {
filterDropdown: 'filterDropdown',
filterIcon: 'filterIcon',
customRender: 'customRender'
},
onFilter: (value: string, record: any) =>
record.address
.toString()
.toLowerCase()
.includes(value.toLowerCase()),
onFilterDropdownVisibleChange: (visible: boolean) => {
if (visible) {
setTimeout(() => {
searchInput.value.focus()
})
}
}
}
])
function handleSearch(selectedKeys: string[], confirm: Function, dataIndex: string) {
confirm()
searchText.value = selectedKeys[0]
searchedColumn.value = dataIndex
ctx.$forceUpdate()
}
function handleReset(clearFilters: Function) {
clearFilters()
searchText.value = ''
}
return {
data,
columns,
searchText,
searchInput,
searchedColumn,
handleSearch,
handleReset
}
}
})
</script>
<style lang="less" scoped>
@{deep}(.highlight) {
background-color: rgb(255, 192, 105);
padding: 0px;
}
</style>

View File

@ -1,98 +0,0 @@
<template>
<div class="editable-cell">
<div v-if="editable" class="editable-cell-input-wrapper">
<a-input v-model:value="value" @pressEnter="check" />
<check-outlined class="editable-cell-icon-check" @click="check" />
</div>
<div v-else class="editable-cell-text-wrapper">
{{ value || ' ' }}
<edit-outlined class="editable-cell-icon" @click="edit" />
</div>
</div>
</template>
<script lang="ts">
import { CheckOutlined, EditOutlined } from '@ant-design/icons-vue'
import { defineComponent, PropType, ref } from 'vue'
export default defineComponent({
name: 'EditableCell',
components: {
CheckOutlined,
EditOutlined
},
props: {
text: {
type: String as PropType<string>,
default: ''
},
onChange: {
type: Function as PropType<Function | null>,
default: null
}
},
setup(props, { emit }) {
const value = ref<string>(props.text)
const editable = ref<boolean>(false)
function handleChange(e: any) {
const value = e.target.value
value.value = value
}
function check() {
editable.value = false
emit('change', value.value)
}
function edit() {
editable.value = true
}
return {
value, editable,
check, edit
}
}
})
</script>
<style lang="less" scoped>
.editable-cell {
position: relative;
.editable-cell-input-wrapper,
.editable-cell-text-wrapper {
padding-right: 24px;
}
.editable-cell-text-wrapper {
padding: 5px 24px 5px 5px;
}
.editable-cell-icon,
.editable-cell-icon-check {
position: absolute;
right: 0;
width: 20px;
cursor: pointer;
}
.editable-cell-icon {
line-height: 18px;
display: none;
}
.editable-cell-icon-check {
line-height: 28px;
}
.editable-cell-icon:hover,
.editable-cell-icon-check:hover {
color: #108ee9;
}
.editable-add-btn {
margin-bottom: 8px;
}
}
.editable-cell:hover .editable-cell-icon {
display: inline-block;
}
</style>

View File

@ -1,104 +0,0 @@
<template>
<div>
<a-button class="editable-add-btn" @click="handleAdd">
Add
</a-button>
<com-table bordered :data-source="dataSource" :columns="columns">
<template #name="{ text, record }">
<editable-cell :text="text" @change="val => onCellChange(record.key, 'name', val)" />
</template>
<template #operation="{ record }">
<a-popconfirm
v-if="dataSource.length"
title="Sure to delete?"
@confirm="onDelete(record.key)"
>
<a>Delete</a>
</a-popconfirm>
</template>
</com-table>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import ComTable from '_c/Table'
import EditableCell from './EditableCell.vue'
export default defineComponent({
// name: 'EditCell',
components: {
ComTable,
EditableCell
},
setup() {
const count = ref<number>(2)
const dataSource = ref<any[]>([
{
key: '0',
name: 'Edward King 0',
age: '32',
address: 'London, Park Lane no. 0'
},
{
key: '1',
name: 'Edward King 1',
age: '32',
address: 'London, Park Lane no. 1'
}
])
const columns = ref<any[]>([
{
title: 'name',
dataIndex: 'name',
width: '30%',
slots: { customRender: 'name' }
},
{
title: 'age',
dataIndex: 'age'
},
{
title: 'address',
dataIndex: 'address'
},
{
title: 'operation',
dataIndex: 'operation',
slots: { customRender: 'operation' }
}
])
function onCellChange(key: string, dataIndex: string, value: string) {
const newDataSource = [...dataSource.value]
const target: any[] = newDataSource.find((item: any) => item.key === key)
if (target) {
target[dataIndex] = value
dataSource.value = newDataSource
}
}
function onDelete(key: string) {
const newDataSource = [...dataSource.value]
dataSource.value = newDataSource.filter((item: any) => item.key !== key)
}
function handleAdd() {
const newData = {
key: count.value,
name: `Edward King ${count.value}`,
age: 32,
address: `London, Park Lane no. ${count.value}`
}
dataSource.value = [...dataSource.value, newData]
count.value = count.value + 1
}
return {
count, dataSource, columns,
onCellChange, onDelete, handleAdd
}
}
})
</script>
<style>
</style>

View File

@ -1,141 +0,0 @@
<template>
<div>
<com-table :columns="columns" :data-source="data" bordered>
<template v-for="col in ['name', 'age', 'address']" #[col]="{ text, record }" :key="col">
<div :key="col">
<a-input
v-if="record.editable"
style="margin: -5px 0"
:value="text"
@change="e => handleChange(e.target.value, record.key, col)"
/>
<template v-else>
{{ text }}
</template>
</div>
</template>
<template #operation="{ record }">
<div class="editable-row-operations">
<span v-if="record.editable">
<a @click="save(record.key)">Save</a>
<a-popconfirm title="Sure to cancel?" @confirm="cancel(record.key)">
<a>Cancel</a>
</a-popconfirm>
</span>
<span v-else>
<a v-bind="editingKey !== '' ? { disabled: 'disabled' } : {}" @click="edit(record.key)">
Edit
</a>
</span>
</div>
</template>
</com-table>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import ComTable from '_c/Table'
const columns = [
{
title: 'name',
dataIndex: 'name',
width: '25%',
slots: { customRender: 'name' }
},
{
title: 'age',
dataIndex: 'age',
width: '15%',
slots: { customRender: 'age' }
},
{
title: 'address',
dataIndex: 'address',
width: '40%',
slots: { customRender: 'address' }
},
{
title: 'operation',
dataIndex: 'operation',
slots: { customRender: 'operation' }
}
]
const dataList: any[] = []
for (let i = 0; i < 100; i++) {
dataList.push({
key: i.toString(),
name: `Edrward ${i}`,
age: 32,
address: `London Park no. ${i}`
})
}
export default defineComponent({
// name: 'EditRow',
components: {
ComTable
},
setup() {
const cacheData = ref<any[]>(dataList.map(item => ({ ...item })))
const editingKey = ref<string>('')
const data = ref<any[]>(dataList)
function handleChange(value: string, key: string, column: string) {
const newData = [...data.value]
const target: any[] = newData.filter((item: any) => key === item.key)[0]
if (target) {
target[column] = value
data.value = newData
}
}
function edit(key: string) {
const newData = [...data.value]
const target: any = newData.filter((item: any) => key === item.key)[0]
editingKey.value = key
if (target) {
target.editable = true
data.value = newData
}
}
function save(key: string) {
const newData = [...data.value]
const newCacheData = [...cacheData.value]
const target: any = newData.filter((item: any) => key === item.key)[0]
const targetCache: any[] = newCacheData.filter((item: any) => key === item.key)[0]
if (target && targetCache) {
delete target.editable
data.value = newData
Object.assign(targetCache, target)
cacheData.value = newCacheData
}
editingKey.value = ''
}
function cancel(key: string) {
const newData = [...data.value]
const target: any = newData.filter((item: any) => key === item.key)[0]
editingKey.value = ''
if (target) {
Object.assign(target, cacheData.value.filter((item: any) => key === item.key)[0])
delete target.editable
data.value = newData
}
}
return {
columns,
data,
editingKey,
handleChange, edit, save, cancel
}
}
})
</script>
<style lang="less" scoped>
.editable-row-operations a {
margin-right: 8px;
}
</style>

View File

@ -0,0 +1,140 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="基于 Element 的 Table 组件进行二次封装,实现数据驱动,支持所有 Table 参数 -- 展开行"
type="info"
style="margin-bottom: 20px;"
/>
<com-table
ref="multipleTable"
v-loading="loading"
:columns="columns"
:data="tableData"
>
<template #id="scope">
<el-form label-position="left" inline class="demo-table-expand">
<el-form-item label="商品名称">
<span>{{ scope.row.name }}</span>
</el-form-item>
<el-form-item label="所属店铺">
<span>{{ scope.row.shop }}</span>
</el-form-item>
<el-form-item label="商品 ID">
<span>{{ scope.row.id }}</span>
</el-form-item>
<el-form-item label="店铺 ID">
<span>{{ scope.row.shopId }}</span>
</el-form-item>
<el-form-item label="商品分类">
<span>{{ scope.row.category }}</span>
</el-form-item>
<el-form-item label="店铺地址">
<span>{{ scope.row.address }}</span>
</el-form-item>
<el-form-item label="商品描述">
<span>{{ scope.row.desc }}</span>
</el-form-item>
</el-form>
</template>
</com-table>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import ComTable from '_c/Table/index.vue'
const columns = [
{
key: 'id',
type: 'expand',
slots: {
default: 'id'
}
},
{
key: 'id',
label: '商品ID'
},
{
key: 'name',
label: '商品名称'
},
{
key: 'desc',
label: '描述'
}
]
const tableData = [
{
id: '12987122',
name: '好滋好味鸡蛋仔',
category: '江浙小吃、小吃零食',
desc: '荷兰优质淡奶,奶香浓而不腻',
address: '上海市普陀区真北路',
shop: '王小虎夫妻店',
shopId: '10333'
}, {
id: '12987123',
name: '好滋好味鸡蛋仔',
category: '江浙小吃、小吃零食',
desc: '荷兰优质淡奶,奶香浓而不腻',
address: '上海市普陀区真北路',
shop: '王小虎夫妻店',
shopId: '10333'
}, {
id: '12987125',
name: '好滋好味鸡蛋仔',
category: '江浙小吃、小吃零食',
desc: '荷兰优质淡奶,奶香浓而不腻',
address: '上海市普陀区真北路',
shop: '王小虎夫妻店',
shopId: '10333'
}, {
id: '12987126',
name: '好滋好味鸡蛋仔',
category: '江浙小吃、小吃零食',
desc: '荷兰优质淡奶,奶香浓而不腻',
address: '上海市普陀区真北路',
shop: '王小虎夫妻店',
shopId: '10333'
}
]
export default defineComponent({
// name: 'ExpandRow',
components: {
ComTable
},
setup() {
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 2000)
return {
columns,
tableData,
loading
}
}
})
</script>
<style lang="less" scoped>
@{deep}(.demo-table-expand) {
font-size: 0;
label {
width: 90px;
color: #99a9bf;
}
.el-form-item {
margin-right: 0;
margin-bottom: 0;
width: 50%;
}
}
</style>

View File

@ -0,0 +1,166 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="基于 Element 的 Table 组件进行二次封装,实现数据驱动,支持所有 Table 参数 -- 固定列和表头"
type="info"
style="margin-bottom: 20px;"
/>
<com-table
v-loading="loading"
:columns="columns"
:data="tableData"
border
height="250"
style="width: 820px;"
>
<template #action="scope">
<el-button type="text" size="small" @click="handleClick(scope.row)">查看</el-button>
<el-button type="text" size="small">编辑</el-button>
</template>
</com-table>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import ComTable from '_c/Table/index.vue'
const columns = [
{
key: 'date',
label: '日期',
fixed: true,
width: '150'
},
{
key: 'name',
label: '姓名',
width: '120'
},
{
key: 'province',
label: '省份',
width: '120'
},
{
key: 'city',
label: '市区',
width: '120'
},
{
key: 'address',
label: '地址',
width: '300'
},
{
key: 'zip',
label: '邮编',
width: '120'
},
{
key: 'action',
label: '操作',
width: '100',
fixed: 'right',
slots: {
default: 'action'
}
}
]
const tableData = [
{
date: '2016-05-02',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
},
{
date: '2016-05-04',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1517 弄',
zip: 200333
},
{
date: '2016-05-01',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1519 弄',
zip: 200333
},
{
date: '2016-05-03',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1516 弄',
zip: 200333
},
{
date: '2016-05-02',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
},
{
date: '2016-05-04',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1517 弄',
zip: 200333
},
{
date: '2016-05-01',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1519 弄',
zip: 200333
},
{
date: '2016-05-03',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1516 弄',
zip: 200333
}
]
export default defineComponent({
// name: 'FixedColumnHeader',
components: {
ComTable
},
setup() {
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 2000)
function handleClick(row: any) {
console.log(row)
}
return {
columns,
tableData,
loading,
handleClick
}
}
})
</script>
<style>
</style>

View File

@ -0,0 +1,133 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="基于 Element 的 Table 组件进行二次封装,实现数据驱动,支持所有 Table 参数 -- 固定列"
type="info"
style="margin-bottom: 20px;"
/>
<com-table
v-loading="loading"
:columns="columns"
:data="tableData"
border
style="width: 820px;"
>
<template #action="scope">
<el-button type="text" size="small" @click="handleClick(scope.row)">查看</el-button>
<el-button type="text" size="small">编辑</el-button>
</template>
</com-table>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import ComTable from '_c/Table/index.vue'
const columns = [
{
key: 'date',
label: '日期',
fixed: true,
width: '150'
},
{
key: 'name',
label: '姓名',
width: '120'
},
{
key: 'province',
label: '省份',
width: '120'
},
{
key: 'city',
label: '市区',
width: '120'
},
{
key: 'address',
label: '地址',
width: '300'
},
{
key: 'zip',
label: '邮编',
width: '120'
},
{
key: 'action',
label: '操作',
width: '100',
fixed: 'right',
slots: {
default: 'action'
}
}
]
const tableData = [
{
date: '2016-05-02',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
},
{
date: '2016-05-04',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1517 弄',
zip: 200333
},
{
date: '2016-05-01',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1519 弄',
zip: 200333
},
{
date: '2016-05-03',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1516 弄',
zip: 200333
}
]
export default defineComponent({
// name: 'FixedColumn',
components: {
ComTable
},
setup() {
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 2000)
function handleClick(row: any) {
console.log(row)
}
return {
columns,
tableData,
loading,
handleClick
}
}
})
</script>
<style>
</style>

View File

@ -1,46 +1,78 @@
<template>
<div>
<com-table :columns="columns" :data-source="data" :scroll="{ x: 1500, y: 300 }">
<template #action>
<a>action</a>
</template>
</com-table>
<el-alert
effect="dark"
:closable="false"
title="基于 Element 的 Table 组件进行二次封装,实现数据驱动,支持所有 Table 参数 -- 固定表头"
type="info"
style="margin-bottom: 20px;"
/>
<com-table
v-loading="loading"
:columns="columns"
:data="tableData"
height="250"
border
/>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import ComTable from '_c/Table'
import { defineComponent, ref } from 'vue'
import ComTable from '_c/Table/index.vue'
const columns = [
{ title: 'Full Name', width: 100, dataIndex: 'name', key: 'name', fixed: 'left' },
{ title: 'Age', width: 100, dataIndex: 'age', key: 'age', fixed: 'left' },
{ title: 'Column 1', dataIndex: 'address', key: '1', width: 6000 },
{ title: 'Column 2', dataIndex: 'address', key: '2', width: 150 },
{ title: 'Column 3', dataIndex: 'address', key: '3', width: 150 },
{ title: 'Column 4', dataIndex: 'address', key: '4', width: 150 },
{ title: 'Column 5', dataIndex: 'address', key: '5', width: 150 },
{ title: 'Column 6', dataIndex: 'address', key: '6', width: 150 },
{ title: 'Column 7', dataIndex: 'address', key: '7', width: 150 },
{ title: 'Column 8', dataIndex: 'address', key: '8' },
{
title: 'Action',
key: 'operation',
fixed: 'right',
width: 100,
slots: { customRender: 'action' }
key: 'date',
label: '日期'
},
{
key: 'name',
label: '姓名'
},
{
key: 'address',
label: '地址'
}
]
const data: any[] = []
for (let i = 0; i < 100; i++) {
data.push({
key: i,
name: `Edrward ${i}`,
age: 32,
address: `London Park no. ${i}`
})
}
const tableData = [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
},
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
},
{
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}
]
export default defineComponent({
// name: 'FixedHeader',
@ -48,9 +80,15 @@ export default defineComponent({
ComTable
},
setup() {
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 2000)
return {
columns,
data
tableData,
loading
}
}
})

View File

@ -0,0 +1,164 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="基于 Element 的 Table 组件进行二次封装,实现数据驱动,支持所有 Table 参数 -- 流体高度"
type="info"
style="margin-bottom: 20px;"
/>
<com-table
v-loading="loading"
:columns="columns"
:data="tableData"
border
max-height="250"
style="width: 820px;"
>
<template #action="scope">
<el-button type="text" size="small" @click="deleteRow(scope.$index)">移除</el-button>
</template>
</com-table>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import ComTable from '_c/Table/index.vue'
const columns = [
{
key: 'date',
label: '日期',
fixed: true,
width: '150'
},
{
key: 'name',
label: '姓名',
width: '120'
},
{
key: 'province',
label: '省份',
width: '120'
},
{
key: 'city',
label: '市区',
width: '120'
},
{
key: 'address',
label: '地址',
width: '300'
},
{
key: 'zip',
label: '邮编',
width: '120'
},
{
key: 'action',
label: '操作',
width: '100',
fixed: 'right',
slots: {
default: 'action'
}
}
]
export default defineComponent({
// name: 'FluidHeight',
components: {
ComTable
},
setup() {
const tableData = ref<any[]>([
{
date: '2016-05-02',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
},
{
date: '2016-05-04',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1517 弄',
zip: 200333
},
{
date: '2016-05-01',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1519 弄',
zip: 200333
},
{
date: '2016-05-03',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1516 弄',
zip: 200333
},
{
date: '2016-05-02',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
},
{
date: '2016-05-04',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1517 弄',
zip: 200333
},
{
date: '2016-05-01',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1519 弄',
zip: 200333
},
{
date: '2016-05-03',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1516 弄',
zip: 200333
}
])
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 2000)
function deleteRow(index: number) {
tableData.value.splice(index, 1)
}
return {
columns,
tableData,
loading,
deleteRow
}
}
})
</script>
<style>
</style>

View File

@ -0,0 +1,162 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="基于 Element 的 Table 组件进行二次封装,实现数据驱动,支持所有 Table 参数 -- 多级表头"
type="info"
style="margin-bottom: 20px;"
/>
<com-table
v-loading="loading"
:columns="columns"
:data="tableData"
>
<template #address="scope">
地址是: {{ scope.row.address }}
</template>
<template #action="scope">
<el-button type="text" size="small" @click="deleteRow(scope.$index)">移除</el-button>
</template>
</com-table>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import ComTable from '_c/Table/index.vue'
const columns = [
{
key: 'date',
label: '日期',
fixed: true,
width: '150'
},
{
label: '配送信息',
children: [
{
key: 'name',
label: '姓名',
width: '120'
},
{
label: '地址',
children: [
{
key: 'province',
label: '省份',
width: '120'
},
{
key: 'city',
label: '市区',
width: '120'
},
{
key: 'address',
label: '地址',
slots: {
default: 'address'
}
},
{
key: 'zip',
label: '邮编',
width: '120'
}
]
}
]
},
{
key: 'action',
label: '操作',
width: '100',
slots: {
default: 'action'
}
}
]
export default defineComponent({
// name: 'MultiHeader',
components: {
ComTable
},
setup() {
const tableData = ref<any[]>([
{
date: '2016-05-03',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-02',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-04',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-01',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-08',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-06',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}, {
date: '2016-05-07',
name: '王小虎',
province: '上海',
city: '普陀区',
address: '上海市普陀区金沙江路 1518 弄',
zip: 200333
}
])
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 2000)
function deleteRow(index: number) {
tableData.value.splice(index, 1)
}
return {
columns,
tableData,
loading,
deleteRow
}
}
})
</script>
<style>
</style>

View File

@ -0,0 +1,102 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="基于 Element 的 Table 组件进行二次封装,实现数据驱动,支持所有 Table 参数 -- 多选"
type="info"
style="margin-bottom: 20px;"
/>
<com-table
ref="multipleTable"
v-loading="loading"
selection
:columns="columns"
:data="tableData"
@selection-change="handleSelectionChange"
/>
<div style="margin-top: 20px">
<el-button @click="toggleSelection([tableData[1], tableData[2]])">切换第二第三行的选中状态</el-button>
<el-button @click="toggleSelection()">取消选择</el-button>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, unref } from 'vue'
import ComTable from '_c/Table/index.vue'
const columns = [
{
key: 'date',
label: '日期'
},
{
key: 'name',
label: '姓名'
},
{
key: 'address',
label: '地址'
}
]
const tableData = [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}
]
export default defineComponent({
// name: 'MultipleChoice',
components: {
ComTable
},
setup() {
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 2000)
const multipleTable = ref<HTMLElement | null>(null)
function toggleSelection(rows: any[]) {
const multipleTableRef = unref(multipleTable as any).getTableRef()
if (rows) {
rows.forEach(row => {
multipleTableRef.toggleRowSelection(row)
})
} else {
multipleTableRef.clearSelection()
}
}
function handleSelectionChange(val: any) {
console.log(val)
}
return {
columns,
tableData,
loading,
multipleTable, toggleSelection, handleSelectionChange
}
}
})
</script>
<style>
</style>

View File

@ -0,0 +1,130 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="基于 Element 的 Table 组件进行二次封装,实现数据驱动,支持所有 Table 参数 -- 筛选"
type="info"
style="margin-bottom: 20px;"
/>
<el-button @click="resetDateFilter">清除日期过滤器</el-button>
<el-button @click="clearFilter">清除所有过滤器</el-button>
<com-table
ref="filterTable"
v-loading="loading"
row-key="date"
:columns="columns"
:data="tableData"
:default-sort="{prop: 'date', order: 'descending'}"
>
<template #tag="scope">
<el-tag
:type="scope.row.tag === '家' ? 'primary' : 'success'"
disable-transitions
>{{ scope.row.tag }}</el-tag>
</template>
</com-table>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, unref } from 'vue'
import ComTable from '_c/Table/index.vue'
const tableData = [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄',
tag: '家'
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄',
tag: '公司'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
tag: '家'
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄',
tag: '公司'
}
]
export default defineComponent({
// name: 'ScreenTable',
components: {
ComTable
},
setup() {
const filterTable = ref<HTMLElement | null>(null)
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 2000)
const columns = ref<any[]>([
{
key: 'date',
label: '日期',
sortable: true,
width: '180',
columnKey: 'date',
filters: [{ text: '2016-05-01', value: '2016-05-01' }, { text: '2016-05-02', value: '2016-05-02' }, { text: '2016-05-03', value: '2016-05-03' }, { text: '2016-05-04', value: '2016-05-04' }],
filterMethod: filterHandler
},
{
key: 'name',
label: '姓名',
sortable: true
},
{
key: 'address',
label: '地址'
},
{
key: 'tag',
label: '标签',
filters: [{ text: '家', value: '家' }, { text: '公司', value: '公司' }],
filterMethod: filterTag,
filterPlacement: 'bottom-end',
slots: {
default: 'tag'
}
}
])
function resetDateFilter() {
const filterTableRef = unref(filterTable as any).getTableRef()
filterTableRef.clearFilter('date')
}
function clearFilter() {
const filterTableRef = unref(filterTable as any).getTableRef()
filterTableRef.clearFilter()
}
function filterTag(value: string, row: any) {
return row.tag === value
}
function filterHandler(value: string, row: any, column: any) {
const property = column['property']
return row[property] === value
}
return {
columns,
tableData,
loading,
filterTable,
resetDateFilter, clearFilter, filterTag, filterHandler
}
}
})
</script>
<style>
</style>

View File

@ -0,0 +1,96 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="基于 Element 的 Table 组件进行二次封装,实现数据驱动,支持所有 Table 参数 -- 单选"
type="info"
style="margin-bottom: 20px;"
/>
<com-table
ref="singleTable"
v-loading="loading"
highlight-current-row
:columns="columns"
:data="tableData"
@current-change="handleCurrentChange"
/>
<div style="margin-top: 20px">
<el-button @click="setCurrent(tableData[1])">选中第二行</el-button>
<el-button @click="setCurrent()">取消选择</el-button>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, unref } from 'vue'
import ComTable from '_c/Table/index.vue'
const columns = [
{
key: 'date',
label: '日期'
},
{
key: 'name',
label: '姓名'
},
{
key: 'address',
label: '地址'
}
]
const tableData = [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}
]
export default defineComponent({
// name: 'SingleChoice',
components: {
ComTable
},
setup() {
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 2000)
const singleTable = ref<HTMLElement | null>(null)
function setCurrent(row: any) {
const singleTableRef = unref(singleTable as any).getTableRef()
singleTableRef.setCurrentRow(row)
}
function handleCurrentChange(val: any) {
console.log(val)
}
return {
columns,
tableData,
loading,
singleTable, setCurrent, handleCurrentChange
}
}
})
</script>
<style>
</style>

View File

@ -0,0 +1,82 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="基于 Element 的 Table 组件进行二次封装,实现数据驱动,支持所有 Table 参数 -- 排序"
type="info"
style="margin-bottom: 20px;"
/>
<com-table
ref="multipleTable"
v-loading="loading"
:columns="columns"
:data="tableData"
:default-sort="{prop: 'date', order: 'descending'}"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import ComTable from '_c/Table/index.vue'
const columns = [
{
key: 'date',
label: '日期',
sortable: true
},
{
key: 'name',
label: '姓名',
sortable: true
},
{
key: 'address',
label: '地址'
}
]
const tableData = [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}
]
export default defineComponent({
// name: 'SortTable',
components: {
ComTable
},
setup() {
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 2000)
return {
columns,
tableData,
loading
}
}
})
</script>
<style>
</style>

View File

@ -0,0 +1,100 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="基于 Element 的 Table 组件进行二次封装,实现数据驱动,支持所有 Table 参数 -- 带状态表格"
type="info"
style="margin-bottom: 20px;"
/>
<com-table
v-loading="loading"
:columns="columns"
:data="tableData"
:row-class-name="tableRowClassName"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import ComTable from '_c/Table/index.vue'
const columns = [
{
key: 'date',
label: '日期'
},
{
key: 'name',
label: '姓名'
},
{
key: 'address',
label: '地址'
}
]
const tableData = [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}
]
export default defineComponent({
// name: 'StateTable',
components: {
ComTable
},
setup() {
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 2000)
function tableRowClassName({ rowIndex }: any) {
if (rowIndex === 1) {
return 'warning-row'
} else if (rowIndex === 3) {
return 'success-row'
}
return ''
}
return {
columns,
tableData,
loading,
tableRowClassName
}
}
})
</script>
<style lang="less" scoped>
@{deep}(.el-table) {
.warning-row {
background: oldlace;
}
}
@{deep}(.el-table) {
.success-row {
background: #f0f9eb;
}
}
</style>

View File

@ -0,0 +1,78 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="基于 Element 的 Table 组件进行二次封装,实现数据驱动,支持所有 Table 参数 -- 带斑马纹表格"
type="info"
style="margin-bottom: 20px;"
/>
<com-table
v-loading="loading"
:columns="columns"
:data="tableData"
stripe
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import ComTable from '_c/Table/index.vue'
const columns = [
{
key: 'date',
label: '日期'
},
{
key: 'name',
label: '姓名'
},
{
key: 'address',
label: '地址'
}
]
const tableData = [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
}, {
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
}, {
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}, {
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}
]
export default defineComponent({
// name: 'StripeTable',
components: {
ComTable
},
setup() {
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 2000)
return {
columns,
tableData,
loading
}
}
})
</script>
<style>
</style>

View File

@ -1,87 +0,0 @@
<template>
<div>
<com-table
:columns="columns"
:data-source="data"
bordered
>
<template #name="{ text }">
<a>{{ text }}</a>
</template>
<template #title>
Header
</template>
<template #footer>
Footer
</template>
</com-table>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import ComTable from '_c/Table'
const columns = [
{
title: 'Name',
dataIndex: 'name',
slots: { customRender: 'name' }
},
{
title: 'Cash Assets',
className: 'column-money',
dataIndex: 'money'
},
{
title: 'Address',
dataIndex: 'address'
}
]
const data = [
{
key: '1',
name: 'John Brown',
money: '¥300,000.00',
address: 'New York No. 1 Lake Park'
},
{
key: '2',
name: 'Jim Green',
money: '¥1,256,000.00',
address: 'London No. 1 Lake Park'
},
{
key: '3',
name: 'Joe Black',
money: '¥120,000.00',
address: 'Sidney No. 1 Lake Park'
}
]
export default defineComponent({
// name: 'TableBorder',
components: {
ComTable
},
setup() {
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 1000)
return {
loading,
columns,
data
}
}
})
</script>
<style lang="less" scoped>
@{deep}(th.column-money),
@{deep}(td.column-money) {
text-align: right;
}
</style>

View File

@ -1,102 +0,0 @@
<template>
<div>
<com-table
:loading="loading"
:columns="columns"
:data-source="data"
>
<template #name="{text}">
<a>{{ text }}</a>
</template>
</com-table>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import ComTable from '_c/Table'
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
slots: { customRender: 'name' }
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
width: 80
},
{
title: 'Address',
dataIndex: 'address',
key: 'address 1',
ellipsis: true
},
{
title: 'Long Column Long Column Long Column',
dataIndex: 'address',
key: 'address 2',
ellipsis: true
},
{
title: 'Long Column Long Column',
dataIndex: 'address',
key: 'address 3',
ellipsis: true
},
{
title: 'Long Column',
dataIndex: 'address',
key: 'address 4',
ellipsis: true
}
]
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park, New York No. 1 Lake Park',
tags: ['nice', 'developer']
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 2 Lake Park, London No. 2 Lake Park',
tags: ['loser']
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park, Sidney No. 1 Lake Park',
tags: ['cool', 'teacher']
}
]
export default defineComponent({
// name: 'TableEllipsis',
components: {
ComTable
},
setup() {
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 1000)
return {
loading,
columns,
data
}
}
})
</script>
<style>
</style>

View File

@ -1,66 +0,0 @@
<template>
<div>
<com-table :columns="columns" :data-source="data">
<template #action>
<a>Delete</a>
</template>
<template #expandedRowRender="{ record }">
<p style="margin: 0">
{{ record.description }}
</p>
</template>
</com-table>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import ComTable from '_c/Table'
const columns = [
{ title: 'Name', dataIndex: 'name', key: 'name' },
{ title: 'Age', dataIndex: 'age', key: 'age' },
{ title: 'Address', dataIndex: 'address', key: 'address' },
{ title: 'Action', dataIndex: '', key: 'x', slots: { customRender: 'action' }}
]
const data = [
{
key: 1,
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
description: 'My name is John Brown, I am 32 years old, living in New York No. 1 Lake Park.'
},
{
key: 2,
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
description: 'My name is Jim Green, I am 42 years old, living in London No. 1 Lake Park.'
},
{
key: 3,
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
description: 'My name is Joe Black, I am 32 years old, living in Sidney No. 1 Lake Park.'
}
]
export default defineComponent({
// name: 'TableExpanded',
components: {
ComTable
},
setup() {
return {
columns,
data
}
}
})
</script>
<style>
</style>

View File

@ -1,107 +0,0 @@
<template>
<div>
<com-table
:columns="columns"
:row-key="record => record.sid"
:data-source="data"
:pagination="pagination"
:loading="loading"
@change="handleTableChange"
>
<template #name="{ text }"> {{ text }} </template>
</com-table>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, onMounted } from 'vue'
import ComTable from '_c/Table'
import api from '_p/index/api'
const columns = [
{
title: 'Name',
dataIndex: 'name',
sorter: true,
// sortDirections: ['ascend', 'descend'],
width: '20%',
slots: { customRender: 'name' }
},
{
title: 'Text',
dataIndex: 'text',
filters: [
{ text: 'Male', value: 'male' },
{ text: 'Female', value: 'female' }
],
width: '60%'
},
{
title: 'Passtime',
dataIndex: 'passtime'
}
]
export default defineComponent({
// name: 'TableLoad',
components: {
ComTable
},
setup() {
const loading = ref<boolean>(true)
const data = ref<any[]>([])
const pagination = ref<Object>({})
onMounted(() => {
fetch()
})
function handleTableChange(pagination: any, filters: any, sorter: any) {
console.log(sorter)
const pager: any = { ...pagination }
pager.current = pagination.current
pagination.value = pager
fetch({
count: pagination.pageSize,
page: pagination.current,
sortField: sorter.field,
sortOrder: sorter.order,
...filters
})
}
async function fetch(params = {
page: 1,
count: 10
}) {
try {
loading.value = true
const res = await api.common.getList({
params
})
if (res) {
const pager: any = { ...pagination.value }
pager.total = 200
data.value = res.result
pagination.value = pager
}
} catch (e) {
console.log(e)
} finally {
loading.value = false
}
}
return {
loading,
columns,
data,
pagination,
handleTableChange
}
}
})
</script>
<style>
</style>

View File

@ -1,151 +0,0 @@
<template>
<div>
<com-table
:columns="columns"
:data-source="data"
bordered
>
<template #name="{ text }">
<a>{{ text }}</a>
</template>
</com-table>
</div>
</template>
<script lang="ts">
import { defineComponent, ref, h } from 'vue'
import ComTable from '_c/Table'
const renderContent = ({ text, index }: any) => {
const obj: any = {
children: text,
props: {}
}
if (index === 4) {
obj.props.colSpan = 0
}
return obj
}
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
tel: '0571-22098909',
phone: 18889898989,
address: 'New York No. 1 Lake Park'
},
{
key: '2',
name: 'Jim Green',
tel: '0571-22098333',
phone: 18889898888,
age: 42,
address: 'London No. 1 Lake Park'
},
{
key: '3',
name: 'Joe Black',
age: 32,
tel: '0575-22098909',
phone: 18900010002,
address: 'Sidney No. 1 Lake Park'
},
{
key: '4',
name: 'Jim Red',
age: 18,
tel: '0575-22098909',
phone: 18900010002,
address: 'London No. 2 Lake Park'
},
{
key: '5',
name: 'Jake White',
age: 18,
tel: '0575-22098909',
phone: 18900010002,
address: 'Dublin No. 2 Lake Park'
}
]
export default defineComponent({
// name: 'TableMerge',
components: {
ComTable
},
setup() {
const columns = ref<any[]>([
{
title: 'Name',
dataIndex: 'name',
customRender: ({ text, index }: any) => {
if (index < 4) {
return h('a', {
attrs: {
href: 'javascript:;'
}
}, text)
}
return {
children: h('a', {
attrs: {
href: 'javascript:;'
}
}, text),
props: {
colSpan: 5
}
}
}
},
{
title: 'Age',
dataIndex: 'age',
customRender: renderContent
},
{
title: 'Home phone',
colSpan: 2,
dataIndex: 'tel',
customRender: ({ text, index }: any) => {
const obj: any = {
children: text,
props: {}
}
if (index === 2) {
obj.props.rowSpan = 2
}
// These two are merged into above cell
if (index === 3) {
obj.props.rowSpan = 0
}
if (index === 4) {
obj.props.colSpan = 0
}
return obj
}
},
{
title: 'Phone',
colSpan: 0,
dataIndex: 'phone',
customRender: renderContent
},
{
title: 'Address',
dataIndex: 'address',
customRender: renderContent
}
])
return {
data,
columns
}
}
})
</script>
<style>
</style>

View File

@ -1,124 +0,0 @@
<template>
<div>
<com-table :columns="columns" :data-source="data" :row-selection="rowSelection" />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import ComTable from '_c/Table'
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name'
},
{
title: 'Age',
dataIndex: 'age',
key: 'age',
width: '12%'
},
{
title: 'Address',
dataIndex: 'address',
width: '30%',
key: 'address'
}
]
const data = [
{
key: 1,
name: 'John Brown sr.',
age: 60,
address: 'New York No. 1 Lake Park',
children: [
{
key: 11,
name: 'John Brown',
age: 42,
address: 'New York No. 2 Lake Park'
},
{
key: 12,
name: 'John Brown jr.',
age: 30,
address: 'New York No. 3 Lake Park',
children: [
{
key: 121,
name: 'Jimmy Brown',
age: 16,
address: 'New York No. 3 Lake Park'
}
]
},
{
key: 13,
name: 'Jim Green sr.',
age: 72,
address: 'London No. 1 Lake Park',
children: [
{
key: 131,
name: 'Jim Green',
age: 42,
address: 'London No. 2 Lake Park',
children: [
{
key: 1311,
name: 'Jim Green jr.',
age: 25,
address: 'London No. 3 Lake Park'
},
{
key: 1312,
name: 'Jimmy Green sr.',
age: 18,
address: 'London No. 4 Lake Park'
}
]
}
]
}
]
},
{
key: 2,
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park'
}
]
const rowSelection = {
onChange: (selectedRowKeys: any, selectedRows: any) => {
console.log(`selectedRowKeys: ${selectedRowKeys}`, 'selectedRows: ', selectedRows)
},
onSelect: (record: any, selected: any, selectedRows: any) => {
console.log(record, selected, selectedRows)
},
onSelectAll: (selected: any, selectedRows: any, changeRows: any) => {
console.log(selected, selectedRows, changeRows)
}
}
export default defineComponent({
// name: 'TableTree',
components: {
ComTable
},
setup() {
return {
columns,
data,
rowSelection
}
}
})
</script>
<style>
</style>

View File

@ -10,7 +10,7 @@
@change="e => setSelectedKeys(e.target.value ? [e.target.value] : [])"
@pressEnter="handleSearch(selectedKeys, confirm, column.dataIndex)"
/>
<a-button
<el-button
type="primary"
size="small"
style="width: 90px; margin-right: 8px"
@ -18,10 +18,10 @@
>
<template #icon><SearchOutlined /></template>
Search
</a-button>
<a-button size="small" style="width: 90px" @click="handleReset(clearFilters)">
</el-button>
<el-button size="small" style="width: 90px" @click="handleReset(clearFilters)">
Reset
</a-button>
</el-button>
</div>
</template>
<template #filterIcon="filtered">

View File

@ -0,0 +1,167 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="基于 Element 的 Table 组件进行二次封装,实现数据驱动,支持所有 Table 参数 -- 表尾合计行"
type="info"
style="margin-bottom: 20px;"
/>
<com-table
v-loading="loading"
:columns="columns"
:data="tableData"
border
show-summary
/>
<com-table
v-loading="loading"
:columns="columns1"
:data="tableData"
border
height="200"
:summary-method="getSummaries"
show-summary
style="margin-top: 20px;"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import ComTable from '_c/Table/index.vue'
const columns = [
{
key: 'id',
label: 'ID'
},
{
key: 'name',
label: '姓名'
},
{
key: 'amount1',
label: '数值1',
sortable: true
},
{
key: 'amount2',
label: '数值2',
sortable: true
},
{
key: 'amount3',
label: '数值4',
sortable: true
}
]
const columns1 = [
{
key: 'id',
label: 'ID'
},
{
key: 'name',
label: '姓名'
},
{
key: 'amount1',
label: '数值1'
},
{
key: 'amount2',
label: '数值2'
},
{
key: 'amount3',
label: '数值4'
}
]
const tableData = [
{
id: '12987122',
name: '王小虎',
amount1: '234',
amount2: '3.2',
amount3: 10
}, {
id: '12987123',
name: '王小虎',
amount1: '165',
amount2: '4.43',
amount3: 12
}, {
id: '12987124',
name: '王小虎',
amount1: '324',
amount2: '1.9',
amount3: 9
}, {
id: '12987125',
name: '王小虎',
amount1: '621',
amount2: '2.2',
amount3: 17
}, {
id: '12987126',
name: '王小虎',
amount1: '539',
amount2: '4.1',
amount3: 15
}
]
export default defineComponent({
// name: 'TotalTable',
components: {
ComTable
},
setup() {
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 2000)
function getSummaries(param: any) {
const { columns, data } = param
const sums: any[] = []
columns.forEach((column: any, index: number) => {
if (index === 0) {
sums[index] = '总价'
return
}
const values = data.map((item: any) => Number(item[column.property]))
if (!values.every((value: number) => isNaN(value))) {
sums[index] = values.reduce((prev: number, curr: number) => {
const value = Number(curr)
if (!isNaN(value)) {
return prev + curr
} else {
return prev
}
}, 0)
sums[index] += ' 元'
} else {
sums[index] = 'N/A'
}
})
return sums
}
return {
columns, columns1,
tableData,
loading,
getSummaries
}
}
})
</script>
<style>
</style>

View File

@ -0,0 +1,181 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="基于 Element 的 Table 组件进行二次封装,实现数据驱动,支持所有 Table 参数 -- 树形数据与懒加载"
type="info"
style="margin-bottom: 20px;"
/>
<com-table
v-loading="loading"
:columns="columns"
:data="tableData"
row-key="id"
border
default-expand-all
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
/>
<com-table
v-loading="loading"
:columns="columns1"
:data="tableData1"
row-key="id"
border
lazy
:load="load"
:tree-props="{children: 'children', hasChildren: 'hasChildren'}"
style="margin-top: 20px;"
/>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
import ComTable from '_c/Table/index.vue'
const columns = [
{
key: 'date',
label: '日期',
sortable: true
},
{
key: 'name',
label: '姓名',
sortable: true
},
{
key: 'address',
label: '地址'
}
]
const columns1 = [
{
key: 'date',
label: '日期'
},
{
key: 'name',
label: '姓名'
},
{
key: 'address',
label: '地址'
}
]
const tableData = [
{
id: 1,
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
},
{
id: 2,
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
},
{
id: 3,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
children: [
{
id: 31,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
},
{
id: 32,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}
]
},
{
id: 4,
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}
]
const tableData1 = [
{
id: 1,
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
},
{
id: 2,
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
},
{
id: 3,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄',
hasChildren: true
},
{
id: 4,
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}
]
export default defineComponent({
// name: 'TreeAndLoad',
components: {
ComTable
},
setup() {
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 2000)
function load(tree: any, treeNode: any, resolve: Function) {
setTimeout(() => {
resolve([
{
id: 31,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}, {
id: 32,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}
])
}, 1000)
}
return {
columns,
columns1,
tableData,
tableData1,
loading,
load
}
}
})
</script>
<style>
</style>

View File

@ -1,234 +0,0 @@
#app {
// 主体区域 Main container
.main-container {
min-height: 100%;
transition: margin-left .28s;
margin-left: @menuWidth;
position: relative;
}
// 侧边栏 Sidebar container
.sidebar-container {
transition: width 0.28s;
width: @menuWidth !important;
height: 100%;
position: fixed;
font-size: 0px;
top: 0;
bottom: 0;
left: 0;
z-index: 1001;
overflow: hidden;
//reset element-ui css
.horizontal-collapse-transition {
transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
}
.scrollbar-wrapper {
overflow-x: hidden !important;
.el-scrollbar__view {
height: 100%;
}
}
.el-scrollbar__bar.is-vertical {
right: 0px;
}
.el-scrollbar {
height: 100%;
}
&.has-logo {
.el-scrollbar {
height: calc(100% - 70px);
}
}
.is-horizontal {
display: none;
}
a {
display: inline-block;
width: 100%;
overflow: hidden;
}
.svg-icon {
margin-right: 16px;
}
.el-menu {
border: none;
height: 100%;
width: 100% !important;
}
// menu hover
.submenu-title-noDropdown,
.el-submenu__title {
color: hsla(0,0%,100%,.7) !important;
&:hover {
// background-color: @menuHover !important;
color: @subMenuActiveText !important;
}
}
.is-active>.el-submenu__title {
color: @subMenuActiveText !important;
}
.is-active {
color: @subMenuActiveText !important;
background-color: @menuActiveBg !important;
&:hover {
color: @subMenuActiveText !important;
background-color: @menuActiveBg !important;
}
& .el-menu-item {
background-color: @menuActiveBg !important;
&:hover {
color: @subMenuActiveText !important;
}
}
}
& .nest-menu .el-submenu>.el-submenu__title,
& .el-submenu .el-menu-item {
min-width: @menuWidth !important;
background-color: @subMenuBg !important;
&:hover {
color: @subMenuActiveText !important;
background-color: @subMenuHover !important;
}
}
& .nest-menu {
& .is-active {
background-color: @menuActiveBg !important;
&:hover {
color: @subMenuActiveText !important;
background-color: @menuActiveBg !important;
}
}
}
}
.hideSidebar {
.sidebar-container {
width: 36px !important;
}
.main-container {
margin-left: 36px;
}
.submenu-title-noDropdown {
padding-left: 10px !important;
position: relative;
.el-tooltip {
padding: 0 10px !important;
}
}
.el-submenu {
overflow: hidden;
&>.el-submenu__title {
padding-left: 10px !important;
.el-submenu__icon-arrow {
display: none;
}
}
}
.el-menu--collapse {
.el-submenu {
&>.el-submenu__title {
&>span {
height: 0;
width: 0;
overflow: hidden;
visibility: hidden;
display: inline-block;
}
}
}
}
}
.el-menu--collapse .el-menu .el-submenu {
min-width: @menuWidth !important;
}
// 适配移动端, Mobile responsive
.mobile {
.main-container {
margin-left: 0px;
}
.sidebar-container {
transition: transform .28s;
width: @menuWidth !important;
}
&.hideSidebar {
.sidebar-container {
pointer-events: none;
transition-duration: 0.3s;
transform: translate3d(-@menuWidth, 0, 0);
}
}
}
.withoutAnimation {
.main-container,
.sidebar-container {
transition: none;
}
}
}
// when menu collapsed
.el-menu--vertical {
&>.el-menu {
.svg-icon {
margin-right: 16px;
}
}
.nest-menu .el-submenu>.el-submenu__title,
.el-menu-item {
&:hover {
// you can use @subMenuHover
// background-color: @menuHover !important;
}
}
// the scroll bar appears when the subMenu is too long
>.el-menu--popup {
max-height: 100vh;
overflow-y: auto;
&::-webkit-scrollbar-track-piece {
background: #d3dce6;
}
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-thumb {
background: #99a9bf;
border-radius: 20px;
}
}
}

View File

@ -35,24 +35,6 @@
}
}
}
.is-active {
color: @menuActiveText !important;
background-color: @subMenuHover !important;
&>.el-submenu__title {
color: @menuActiveText !important;
}
}
.nest-menu {
background-color: @subMenuBg !important;
.el-submenu>.el-submenu__title {
background-color: @subMenuBg !important;
}
.is-active {
background-color: @subMenuHover !important;
}
}
// menu hover
.submenu-title-noDropdown,
.el-submenu__title {
@ -65,6 +47,28 @@
color: @subMenuActiveText !important;
// background-color: @subMenuHover !important;
}
.is-active {
color: @menuActiveText !important;
background-color: @subMenuHover !important;
&:hover {
color: @subMenuActiveText !important;
background-color: @subMenuHover !important;
}
&>.el-submenu__title {
color: @menuActiveText !important;
}
}
// .nest-menu {
// background-color: @subMenuBg !important;
// .el-submenu>.el-submenu__title {
// background-color: @subMenuBg !important;
// }
// .is-active {
// background-color: @subMenuHover !important;
// }
// }
}
.el-menu--collapse {
&>div>.el-submenu {
@ -103,11 +107,11 @@
}
}
.nest-menu {
.is-active {
background-color: @subMenuHover !important;
}
}
// .nest-menu {
// .is-active {
// background-color: @subMenuHover !important;
// }
// }
// menu hover
.submenu-title-noDropdown,
.el-submenu__title {
@ -149,15 +153,15 @@
}
}
.nest-menu {
background-color: @subMenuBg !important;
.el-submenu>.el-submenu__title {
background-color: @subMenuBg !important;
}
.is-active {
background-color: @subMenuHover !important;
}
}
// .nest-menu {
// background-color: @subMenuBg !important;
// .el-submenu>.el-submenu__title {
// background-color: @subMenuBg !important;
// }
// .is-active {
// background-color: @subMenuHover !important;
// }
// }
// menu hover
.submenu-title-noDropdown,
.el-submenu__title {
@ -191,11 +195,11 @@
}
}
.nest-menu {
.is-active {
// background-color: @subMenuHover !important;
}
}
// .nest-menu {
// .is-active {
// // background-color: @subMenuHover !important;
// }
// }
// menu hover
.submenu-title-noDropdown,
.el-submenu__title {

View File

@ -3990,10 +3990,10 @@ electron-to-chromium@^1.3.621:
resolved "https://registry.npm.taobao.org/electron-to-chromium/download/electron-to-chromium-1.3.622.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Felectron-to-chromium%2Fdownload%2Felectron-to-chromium-1.3.622.tgz#9726bd2e67a5462154750ce9701ca6af07d07877"
integrity sha1-lya9LmelRiFUdQzpcBymrwfQeHc=
element-plus@^1.0.1-beta.8:
version "1.0.1-beta.8"
resolved "https://registry.yarnpkg.com/element-plus/-/element-plus-1.0.1-beta.8.tgz#d64ead88cff37274262bfd9196fe4657f55149ed"
integrity sha512-D1u65HYMnT44U3cmll66W4mEhxLPZTqcziJtaWwdtl6VIEjrUwnrkCScnH5mVw3w/3NwRHW6XiqZCFxqafUvhw==
element-plus@1.0.1-beta.10:
version "1.0.1-beta.10"
resolved "https://registry.yarnpkg.com/element-plus/-/element-plus-1.0.1-beta.10.tgz#3fdc914c4a888b899d701642e13d1904ccae01f0"
integrity sha512-yjiU9Nnt8VyKJR6L2JoSa59W9pMX4A/Q6csXbd3OJ2ZcakalCP1iDGhAWmp6WQOkgmJFhhhyLENDd5HH1d5Zdw==
dependencies:
"@popperjs/core" "^2.4.4"
async-validator "^3.4.0"