release: template版本发布

This commit is contained in:
kailong321200875
2021-10-24 13:03:37 +08:00
parent b79a56753d
commit 2a7f3d2c46
92 changed files with 75 additions and 9281 deletions

View File

@@ -1,50 +0,0 @@
/**
* request全局配置
*/
const config: {
base_url: {
base: string
dev: string
pro: string
test: string
}
result_code: number | string
default_headers: 'application/json' | 'application/x-www-form-urlencoded' | 'multipart/form-data'
request_timeout: number
} = {
/**
* api请求基础路径
*/
base_url: {
// 开发环境接口前缀
base: 'http://mockjs.test.cn',
// 打包开发环境接口前缀
dev: 'http://mockjs.test.cn',
// 打包生产环境接口前缀
pro: 'http://mockjs.test.cn',
// 打包测试环境接口前缀
test: 'http://mockjs.test.cn'
},
/**
* 接口成功返回状态码
*/
result_code: '0000',
/**
* 接口请求超时时间
*/
request_timeout: 60000,
/**
* 默认接口请求类型
* 可选值application/x-www-form-urlencoded multipart/form-data
*/
default_headers: 'application/json'
}
export default config

View File

@@ -1,32 +0,0 @@
import request from './request'
import { useAppStoreWithOut } from '@/store/modules/app'
const appStore = useAppStoreWithOut()
import config from './config'
import { AxiosPromise } from 'axios'
const { default_headers } = config
function fetch({
url,
method,
params,
data,
headersType,
responseType
}: FetchConfig): AxiosPromise {
return request({
url: url,
method,
params: appStore.getRequestTime ? { time: new Date().getTime(), ...(params || {}) } : params,
data,
responseType: responseType,
headers: {
'Content-Type': headersType || default_headers
}
})
}
export default fetch

View File

@@ -1,70 +0,0 @@
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
import { ElMessage } from 'element-plus'
import qs from 'qs'
import config from './config'
const { result_code, base_url } = config
export const PATH_URL = base_url[import.meta.env.VITE_API_BASEPATH as string]
// 创建axios实例
const service: AxiosInstance = axios.create({
baseURL: PATH_URL, // api 的 base_url
timeout: config.request_timeout // 请求超时时间
})
// request拦截器
service.interceptors.request.use(
(config: AxiosRequestConfig) => {
if (
config.method === 'post' &&
(config.headers as any)['Content-Type'] === 'application/x-www-form-urlencoded'
) {
config.data = qs.stringify(config.data)
}
// get参数编码
if (config.method === 'get' && config.params) {
let url = config.url as string
url += '?'
const keys = Object.keys(config.params)
for (const key of keys) {
if (config.params[key] !== void 0 && config.params[key] !== null) {
url += `${key}=${encodeURIComponent(config.params[key])}&`
}
}
url = url.substring(0, url.length - 1)
config.params = {}
config.url = url
}
return config
},
(error: AxiosError) => {
// Do something with request error
console.log(error) // for debug
Promise.reject(error)
}
)
// response 拦截器
service.interceptors.response.use(
(response: AxiosResponse<any>) => {
if (response.config.responseType === 'blob') {
// 如果是文件流,直接过
return response
} else if (response.data.code === result_code) {
return response.data
} else {
ElMessage.error(response.data.message)
}
},
(error: AxiosError) => {
console.log('err' + error) // for debug
ElMessage.error(error.message)
return Promise.reject(error)
}
)
export default service

View File

@@ -1,141 +0,0 @@
<template>
<div class="avatars-wrap">
<template v-if="tooltip">
<el-tooltip
v-for="(item, $index) in avatarsData"
:key="$index"
:content="item.text"
placement="top"
>
<div
:class="
showAvatar ? 'avatars-item-img' : ['avatars-item', `avatars-${item.type || 'default'}`]
"
>
<el-avatar v-if="showAvatar" :size="40" :src="item.url">
<img :src="defaultImg" />
</el-avatar>
<span v-else>{{ item.text.substr(0, 1) }}</span>
</div>
</el-tooltip>
<div v-if="max && data.length - max > 0" :class="['avatars-item', 'avatars-item-img']">
<span>+{{ data.length - max }}</span>
</div>
</template>
<template v-else>
<div
v-for="(item, $index) in avatarsData"
:key="$index"
:class="
showAvatar ? 'avatars-item-img' : ['avatars-item', `avatars-${item.type || 'default'}`]
"
>
<el-avatar v-if="showAvatar" :size="40" :src="item.url">
<img :src="defaultImg" />
</el-avatar>
<span v-else>{{ item.text.substr(0, 1) }}</span>
</div>
<div v-if="max && data.length - max > 0" :class="['avatars-item', 'avatars-item-img']">
<span>+{{ data.length - max }}</span>
</div>
</template>
</div>
</template>
<script setup lang="ts" name="Avatars">
import { PropType, computed } from 'vue'
import { deepClone } from '@/utils'
import { AvatarConfig } from './types'
import defaultImg from '@/assets/img/default-avatar.png'
const props = defineProps({
// 展示的数据
data: {
type: Array as PropType<AvatarConfig[]>,
default: () => []
},
// 最大展示数量
max: {
type: Number as PropType<number>,
default: 0
},
// 是否使用头像
showAvatar: {
type: Boolean as PropType<boolean>,
default: false
},
// 是否显示完整名称
tooltip: {
type: Boolean as PropType<boolean>,
default: true
}
})
const avatarsData = computed(() => {
if (props.max) {
if (props.data.length <= props.max) {
return props.data
} else {
const data = deepClone(props.data).splice(0, props.max)
return data
}
} else {
return props.data
}
})
</script>
<style lang="less" scoped>
.avatars-wrap {
display: flex;
.avatars-item {
display: inline-block;
width: 40px;
height: 40px;
line-height: 40px;
color: #fff;
text-align: center;
background: #2d8cf0;
border: 1px solid #fff;
border-radius: 50%;
}
.avatars-item-img {
display: inline-block;
border-radius: 50%;
.el-avatar--circle {
border: 1px solid #fff;
}
}
.avatars-item-img + .avatars-item-img {
margin-left: -12px;
}
.avatars-item + .avatars-item {
margin-left: -12px;
}
.avatars-default {
color: #bae7ff;
background: #096dd9;
}
.avatars-success {
color: #f6ffed;
background: #52c41a;
}
.avatars-danger {
color: #fff1f0;
background: #f5222d;
}
.avatars-warning {
color: #fffbe6;
background: #faad14;
}
}
</style>

View File

@@ -1,5 +0,0 @@
export interface AvatarConfig {
text: string
type?: string
url?: string
}

View File

@@ -1,156 +0,0 @@
<template>
<span>
{{ displayValue }}
</span>
</template>
<script setup lang="ts" name="CountTo">
import { reactive, computed, watch, onMounted, unref, toRef } from 'vue'
import { countToProps } from './props'
import { isNumber } from '@/utils/validate'
const props = defineProps(countToProps)
const emit = defineEmits(['mounted', 'callback'])
defineExpose({
pauseResume,
reset,
start,
pause
})
const state = reactive<{
localStartVal: number
printVal: number | null
displayValue: string
paused: boolean
localDuration: number | null
startTime: number | null
timestamp: number | null
rAF: any
remaining: number | null
}>({
localStartVal: props.startVal,
displayValue: formatNumber(props.startVal),
printVal: null,
paused: false,
localDuration: props.duration,
startTime: null,
timestamp: null,
remaining: null,
rAF: null
})
const displayValue = toRef(state, 'displayValue')
onMounted(() => {
if (props.autoplay) {
start()
}
emit('mounted')
})
const getCountDown = computed(() => {
return props.startVal > props.endVal
})
watch([() => props.startVal, () => props.endVal], () => {
if (props.autoplay) {
start()
}
})
function start() {
const { startVal, duration } = props
state.localStartVal = startVal
state.startTime = null
state.localDuration = duration
state.paused = false
state.rAF = requestAnimationFrame(count)
}
function pauseResume() {
if (state.paused) {
resume()
state.paused = false
} else {
pause()
state.paused = true
}
}
function pause() {
cancelAnimationFrame(state.rAF)
}
function resume() {
state.startTime = null
state.localDuration = +(state.remaining as number)
state.localStartVal = +(state.printVal as number)
requestAnimationFrame(count)
}
function reset() {
state.startTime = null
cancelAnimationFrame(state.rAF)
state.displayValue = formatNumber(props.startVal)
}
function count(timestamp: number) {
const { useEasing, easingFn, endVal } = props
if (!state.startTime) state.startTime = timestamp
state.timestamp = timestamp
const progress = timestamp - state.startTime
state.remaining = (state.localDuration as number) - progress
if (useEasing) {
if (unref(getCountDown)) {
state.printVal =
state.localStartVal -
easingFn(progress, 0, state.localStartVal - endVal, state.localDuration as number)
} else {
state.printVal = easingFn(
progress,
state.localStartVal,
endVal - state.localStartVal,
state.localDuration as number
)
}
} else {
if (unref(getCountDown)) {
state.printVal =
state.localStartVal -
(state.localStartVal - endVal) * (progress / (state.localDuration as number))
} else {
state.printVal =
state.localStartVal +
(endVal - state.localStartVal) * (progress / (state.localDuration as number))
}
}
if (unref(getCountDown)) {
state.printVal = state.printVal < endVal ? endVal : state.printVal
} else {
state.printVal = state.printVal > endVal ? endVal : state.printVal
}
state.displayValue = formatNumber(state.printVal)
if (progress < (state.localDuration as number)) {
state.rAF = requestAnimationFrame(count)
} else {
emit('callback')
}
}
function formatNumber(num: number | string) {
const { decimals, decimal, separator, suffix, prefix } = props
num = Number(num).toFixed(decimals)
num += ''
const x = num.split('.')
let x1 = x[0]
const x2 = x.length > 1 ? decimal + x[1] : ''
const rgx = /(\d+)(\d{3})/
if (separator && !isNumber(separator)) {
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + separator + '$2')
}
}
return prefix + x1 + x2 + suffix
}
</script>

View File

@@ -1,62 +0,0 @@
import { PropType } from 'vue'
export const countToProps = {
startVal: {
type: Number as PropType<number>,
required: false,
default: 0
},
endVal: {
type: Number as PropType<number>,
required: false,
default: 2017
},
duration: {
type: Number as PropType<number>,
required: false,
default: 3000
},
autoplay: {
type: Boolean as PropType<boolean>,
required: false,
default: true
},
decimals: {
type: Number as PropType<number>,
required: false,
default: 0,
validator(value: number) {
return value >= 0
}
},
decimal: {
type: String as PropType<string>,
required: false,
default: '.'
},
separator: {
type: String as PropType<string>,
required: false,
default: ','
},
prefix: {
type: String as PropType<string>,
required: false,
default: ''
},
suffix: {
type: String as PropType<string>,
required: false,
default: ''
},
useEasing: {
type: Boolean as PropType<boolean>,
required: false,
default: true
},
easingFn: {
type: Function as PropType<(t: number, b: number, c: number, d: number) => number>,
default(t: number, b: number, c: number, d: number) {
return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b
}
}
}

View File

@@ -1,215 +0,0 @@
<template>
<div ref="editorRef"></div>
</template>
<script setup lang="ts" name="Editor">
import { PropType, watch, computed, onMounted, onBeforeUnmount, ref, unref } from 'vue'
import E from 'wangeditor'
import hljs from 'highlight.js' // 这个蠢货插件9以上的版本都不支持IE。辣鸡
import 'highlight.js/styles/monokai-sublime.css'
import { oneOf } from '@/utils'
import { EditorConfig } from './types'
import { Message } from '_c/Message'
const props = defineProps({
config: {
type: Object as PropType<EditorConfig>,
default: () => {
return {}
}
},
valueType: {
type: String as PropType<'html' | 'text'>,
default: 'html',
validator: (val: string) => {
return oneOf(val, ['html', 'text'])
}
},
value: {
type: String as PropType<string>,
default: ''
}
})
const emit = defineEmits(['change', 'focus', 'blur'])
defineExpose({
getHtml,
getJSON,
getText
})
let editor: Nullable<E> = null
const value = computed(() => props.value)
const editorRef = ref<Nullable<HTMLElement>>(null)
watch(
value,
(val: string) => {
if (editor) {
editor.txt.html(val)
}
},
{
immediate: true
}
)
function createdEditor() {
editor = new E(unref(editorRef.value) as HTMLElement)
initConfig()
editor.create()
editor.txt.html(value.value)
}
function initConfig() {
const config = props.config as EditorConfig
const editorRef = editor as E
// // 设置编辑区域高度为 500px
editorRef.config.height = config.height || 500
// // 设置zIndex
editorRef.config.zIndex = config.zIndex || 0
// // 设置 placeholder 提示文字
editorRef.config.placeholder = config.placeholder || '请输入文本'
// // 设置是否自动聚焦
editorRef.config.focus = config.focus || false
// 配置菜单
editorRef.config.menus = config.menus || [
'head',
'bold',
'fontSize',
'fontName',
'italic',
'underline',
'strikeThrough',
'indent',
'lineHeight',
'foreColor',
'backColor',
'link',
'list',
'justify',
'quote',
'emoticon',
'image',
'video',
'table',
'code',
'splitLine',
'undo',
'redo'
]
// 配置颜色(文字颜色、背景色)
editorRef.config.colors = config.colors || ['#000000', '#eeece0', '#1c487f', '#4d80bf']
// 配置字体
editorRef.config.fontNames = config.fontNames || [
'黑体',
'仿宋',
'楷体',
'标楷体',
'华文仿宋',
'华文楷体',
'宋体',
'微软雅黑',
'Arial',
'Tahoma',
'Verdana',
'Times New Roman',
'Courier New'
]
// 配置行高
editorRef.config.lineHeights = config.lineHeights || ['1', '1.15', '1.6', '2', '2.5', '3']
// // 代码高亮
editorRef.highlight = hljs
// // 配置全屏
editorRef.config.showFullScreen = config.showFullScreen || true
// 编辑器 customAlert 是对全局的alert做了统一处理默认为 window.alert。
// 如觉得浏览器自带的alert体验不佳可自定义 alert以便于达到与自身项目统一的alert效果。
editorRef.config.customAlert =
config.customAlert ||
function (s: string, t: string) {
switch (t) {
case 'success':
Message.success(s)
break
case 'info':
Message.info(s)
break
case 'warning':
Message.warning(s)
break
case 'error':
Message.error(s)
break
default:
Message.info(s)
break
}
}
// 图片上传默认使用base64
editorRef.config.uploadImgShowBase64 = true
// 配置 onchange 回调函数
editorRef.config.onchange = (html: string) => {
const text = editorRef.txt.text()
emitFun(editor, props.valueType === 'html' ? html : text, 'change')
}
// 配置触发 onchange 的时间频率,默认为 200ms
editorRef.config.onchangeTimeout = config.onchangeTimeout || 1000
// 编辑区域 focus聚焦和 blur失焦时触发的回调函数。
editorRef.config.onblur = (html: string) => {
emitFun(editor, html, 'blur')
}
editorRef.config.onfocus = (html: string) => {
emitFun(editor, html, 'focus')
}
}
function emitFun(editor: any, _: string, type: 'change' | 'focus' | 'blur'): void {
if (editor) {
emit(type, props.valueType === 'html' ? (editor as E).txt.html() : (editor as E).txt.text())
}
}
function getHtml() {
if (editor) {
return (editor as E).txt.html()
}
}
function getText() {
if (editor) {
return (editor as E).txt.text()
}
}
function getJSON() {
if (editor) {
return (editor as E).txt.getJSON()
}
}
onMounted(() => {
createdEditor()
})
onBeforeUnmount(() => {
if (editor) {
;(editor as E).destroy()
editor = null
}
})
</script>

View File

@@ -1,13 +0,0 @@
export interface EditorConfig {
height?: number // 富文本高度
zIndex?: number // 层级
placeholder?: string // 提示文字
focus?: boolean // 是否聚焦
onchangeTimeout?: number // 几秒监听一次变化
customAlert?: (s: string, t: string) => {} // 自定义提示
menus?: string[] // 按钮菜单
colors?: string[] // 颜色
fontNames?: string[] // 字体
lineHeights?: string[] // 行间距
showFullScreen?: boolean // 是否全屏
}

View File

@@ -1,68 +0,0 @@
import { defineComponent, PropType, computed, h } from 'vue'
export default defineComponent({
name: 'Highlight',
props: {
tag: {
type: String as PropType<string>,
default: 'span'
},
keys: {
type: Array as PropType<string[]>,
default: () => []
},
color: {
type: String as PropType<string>,
default: '#2d8cf0'
}
},
emits: ['click'],
setup(props, { emit }) {
const keyNodes = computed(() => {
return props.keys.map((key) => {
return h(
'span',
{
onClick: () => {
emit('click', key)
},
style: {
color: props.color,
cursor: 'pointer'
}
},
key
)
})
})
function parseText(text: string) {
props.keys.forEach((key, index) => {
const regexp = new RegExp(key, 'g')
text = text.replace(regexp, `{{${index}}}`)
})
return text.split(/{{|}}/)
}
return {
keyNodes,
parseText
}
},
render(props: any) {
if (!props.$slots.default) return null
const node = props.$slots.default()[0].children
if (!node) {
console.warn('Highlight组件的插槽必须要是文本')
return props.$slots.default()[0]
}
const textArray = props.parseText(node)
const regexp = /^[0-9]*$/
const nodes = textArray.map((t: any) => {
if (regexp.test(t)) {
return props.keyNodes[Math.floor(t)] || t
}
return t
})
return h(props.tag, nodes)
}
})

View File

@@ -1,33 +0,0 @@
import ImgPreview from './index.vue'
import { isClient } from '@/utils/validate'
import type { Options, Props } from './types'
import { createVNode, render } from 'vue'
let instance: any = null
export function createImgPreview(options: Options) {
if (!isClient) return
const {
imageList,
show = true,
index = 0,
onSelect = null,
onClose = null,
zIndex = 500
} = options
const propsData: Partial<Props> = {}
const container = document.createElement('div')
propsData.imageList = imageList
propsData.show = show
propsData.index = index
propsData.zIndex = zIndex
propsData.onSelect = onSelect
propsData.onClose = onClose
document.body.appendChild(container)
instance = createVNode(ImgPreview, propsData)
render(instance, container)
}

View File

@@ -1,429 +0,0 @@
<template>
<transition name="viewer-fade">
<div
v-show="show"
ref="wrapElRef"
tabindex="-1"
:style="{ 'z-index': zIndex }"
class="image-viewer__wrapper"
>
<div class="image-viewer__mask"></div>
<!-- CLOSE -->
<span class="image-viewer__btn image-viewer__close" @click="hide">
<i class="el-icon-circle-close iconfont"></i>
</span>
<!-- ARROW -->
<template v-if="!isSingle">
<span
class="image-viewer__btn image-viewer__prev"
:class="{ 'is-disabled': !infinite && isFirst }"
@click="prev"
>
<i class="el-icon-arrow-left iconfont"></i>
</span>
<span
class="image-viewer__btn image-viewer__next"
:class="{ 'is-disabled': !infinite && isLast }"
@click="next"
>
<i class="el-icon-arrow-right iconfont"></i>
</span>
</template>
<!-- ACTIONS -->
<div class="image-viewer__btn image-viewer__actions">
<div class="image-viewer__actions__inner">
<svg-icon class="iconfont" icon-class="unscale" @click="handleActions('zoomOut')" />
<svg-icon class="iconfont" icon-class="scale" @click="handleActions('zoomIn')" />
<svg-icon class="iconfont" icon-class="resume" @click="toggleMode" />
<svg-icon
class="iconfont"
icon-class="unrotate"
@click="handleActions('anticlocelise')"
/>
<svg-icon class="iconfont" icon-class="rotate" @click="handleActions('clocelise')" />
</div>
</div>
<!-- CANVAS -->
<div class="image-viewer__canvas">
<img
ref="imgRef"
:src="currentImg"
:style="imgStyle"
class="image-viewer__img"
@load="handleImgLoad"
@error="handleImgError"
@mousedown="handleMouseDown"
@click="select"
/>
</div>
</div>
</transition>
</template>
<script setup lang="ts" name="Preview">
import { ref, reactive, computed, watch, nextTick, unref } from 'vue'
import { previewProps } from './props'
import { isFirefox } from '@/utils/validate'
import { on, off } from '@/utils/dom-utils'
import { throttle } from 'lodash-es'
import SvgIcon from '_c/SvgIcon/index.vue'
const mousewheelEventName = isFirefox() ? 'DOMMouseScroll' : 'mousewheel'
const props = defineProps(previewProps)
const infinite = ref<boolean>(true)
const loading = ref<boolean>(false)
const show = ref<boolean>(props.show)
const index = ref<number>(props.index)
const transform = reactive({
scale: 1,
deg: 0,
offsetX: 0,
offsetY: 0,
enableTransition: false
})
const isSingle = computed((): boolean => props.imageList.length <= 1)
const isFirst = computed((): boolean => index.value === 0)
const isLast = computed((): boolean => index.value === props.imageList.length - 1)
const currentImg = computed((): string => props.imageList[index.value])
const imgStyle = computed(() => {
const { scale, deg, offsetX, offsetY, enableTransition } = transform
const style = {
transform: `scale(${scale}) rotate(${deg}deg)`,
transition: enableTransition ? 'transform .3s' : '',
'margin-left': `${offsetX}px`,
'margin-top': `${offsetY}px`
}
return style
})
const wrapElRef = ref<HTMLElement | null>(null)
const imgRef = ref<HTMLElement | null>(null)
let _keyDownHandler: Function | null = null
let _mouseWheelHandler: Function | null = null
let _dragHandler: Function | null = null
watch(
() => index.value,
() => {
reset()
}
)
watch(
() => currentImg.value,
() => {
nextTick(() => {
const $img = unref(imgRef) as any
if (!$img.complete) {
loading.value = true
}
})
}
)
watch(
() => show.value,
(show: boolean) => {
if (show) {
nextTick(() => {
;(unref(wrapElRef) as any).focus()
document.body.style.overflow = 'hidden'
deviceSupportInstall()
})
} else {
nextTick(() => {
document.body.style.overflow = 'auto'
deviceSupportUninstall()
})
}
},
{
immediate: true
}
)
function hide(): void {
show.value = false
if (typeof props.onClose === 'function') {
props.onClose(index.value)
}
}
function select(): void {
if (typeof props.onSelect === 'function') {
props.onSelect(index.value)
}
}
function deviceSupportInstall(): void {
_keyDownHandler = throttle((e: any) => {
const keyCode = e.keyCode
switch (keyCode) {
// ESC
case 27:
hide()
break
// SPACE
case 32:
toggleMode()
break
// LEFT_ARROW
case 37:
prev()
break
// UP_ARROW
case 38:
handleActions('zoomIn')
break
// RIGHT_ARROW
case 39:
next()
break
// DOWN_ARROW
case 40:
handleActions('zoomOut')
break
}
})
_mouseWheelHandler = throttle((e: any) => {
const delta = e.wheelDelta ? e.wheelDelta : -e.detail
if (delta > 0) {
handleActions('zoomIn', {
zoomRate: 0.015,
enableTransition: false
})
} else {
handleActions('zoomOut', {
zoomRate: 0.015,
enableTransition: false
})
}
})
on(document, 'keydown', _keyDownHandler as any)
on(document, mousewheelEventName, _mouseWheelHandler as any)
}
function deviceSupportUninstall(): void {
off(document, 'keydown', _keyDownHandler)
off(document, mousewheelEventName, _mouseWheelHandler)
_keyDownHandler = null
_mouseWheelHandler = null
}
function handleImgLoad(): void {
loading.value = false
}
function handleImgError(e: any): void {
loading.value = false
e.target.alt = '加载失败'
}
function handleMouseDown(e: any): void {
if (loading.value || e.button !== 0) return
const { offsetX, offsetY } = transform
const startX = e.pageX
const startY = e.pageY
_dragHandler = throttle((ev: any) => {
transform.offsetX = offsetX + ev.pageX - startX
transform.offsetY = offsetY + ev.pageY - startY
})
on(document, 'mousemove', _dragHandler as any)
on(document, 'mouseup', () => {
off(document, 'mousemove', _dragHandler as any)
})
e.preventDefault()
}
function reset(): void {
transform.scale = 1
transform.deg = 0
transform.offsetX = 0
transform.offsetY = 0
transform.enableTransition = false
}
function toggleMode(): void {
if (loading.value) return
reset()
}
function prev(): void {
if (isFirst.value && !infinite.value) return
const len = props.imageList.length
index.value = (index.value - 1 + len) % len
}
function next(): void {
if (isLast.value && !infinite.value) return
const len = props.imageList.length
index.value = (index.value + 1) % len
}
function handleActions(action: string, options: any = {}): void {
if (loading.value) return
const style = {
zoomRate: 0.2,
rotateDeg: 90,
enableTransition: true,
...options
}
const { zoomRate, rotateDeg, enableTransition } = style
switch (action) {
case 'zoomOut':
if (transform.scale > 0.2) {
transform.scale = parseFloat((transform.scale - zoomRate).toFixed(3))
}
break
case 'zoomIn':
transform.scale = parseFloat((transform.scale + zoomRate).toFixed(3))
break
case 'clocelise':
transform.deg += rotateDeg
break
case 'anticlocelise':
transform.deg -= rotateDeg
break
}
transform.enableTransition = enableTransition
}
</script>
<style lang="less" scoped>
.iconfont {
cursor: pointer;
}
.image-viewer__wrapper {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
}
.image-viewer__btn {
position: absolute;
z-index: 1;
display: flex;
cursor: pointer;
border-radius: 50%;
opacity: 0.8;
box-sizing: border-box;
user-select: none;
align-items: center;
justify-content: center;
}
.image-viewer__close {
top: 40px;
right: 40px;
width: 40px;
height: 40px;
font-size: 40px;
color: #fff;
}
.image-viewer__canvas {
display: flex;
width: 100%;
height: 100%;
justify-content: center;
align-items: center;
}
.image-viewer__actions {
bottom: 30px;
left: 50%;
width: 282px;
height: 44px;
padding: 0 23px;
background-color: #606266;
border-color: #fff;
border-radius: 22px;
transform: translateX(-50%);
.image-viewer__actions__inner {
display: flex;
width: 100%;
height: 100%;
font-size: 23px;
color: #fff;
text-align: justify;
cursor: default;
align-items: center;
justify-content: space-around;
}
}
.image-viewer__prev {
top: 50%;
left: 40px;
width: 44px;
height: 44px;
font-size: 24px;
color: #fff;
background-color: #606266;
border-color: #fff;
transform: translateY(-50%);
}
.image-viewer__next {
top: 50%;
right: 40px;
width: 44px;
height: 44px;
font-size: 24px;
color: #fff;
text-indent: 2px;
background-color: #606266;
border-color: #fff;
transform: translateY(-50%);
}
.image-viewer__mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: #000;
opacity: 0.5;
}
.viewer-fade-enter-active {
animation: viewer-fade-in 0.3s;
}
.viewer-fade-leave-active {
animation: viewer-fade-out 0.3s;
}
@keyframes viewer-fade-in {
0% {
opacity: 0;
transform: translate3d(0, -20px, 0);
}
100% {
opacity: 1;
transform: translate3d(0, 0, 0);
}
}
@keyframes viewer-fade-out {
0% {
opacity: 1;
transform: translate3d(0, 0, 0);
}
100% {
opacity: 0;
transform: translate3d(0, -20px, 0);
}
}
</style>

View File

@@ -1,28 +0,0 @@
import { PropType } from 'vue'
export const previewProps = {
index: {
type: Number as PropType<number>,
default: 0
},
zIndex: {
type: Number as PropType<number>,
default: 100
},
show: {
type: Boolean as PropType<boolean>,
default: false
},
imageList: {
type: [Array] as PropType<string[]>,
default: []
},
onClose: {
type: Function as PropType<Function>,
default: null
},
onSelect: {
type: Function as PropType<Function>,
default: null
}
}

View File

@@ -1,18 +0,0 @@
export interface Options {
show?: boolean
imageList: string[]
index?: number
zIndex?: number
onSelect?: Function | null
onClose?: Function | null
}
export interface Props {
show: boolean
instance: Props
imageList: string[]
index: number
zIndex: number
onSelect: Function | null
onClose: Function | null
}

View File

@@ -1,270 +0,0 @@
<template>
<div v-loading="loading" class="qrcode__wrap" :style="wrapStyle">
<component :is="tag" ref="wrapRef" @click="clickCode" />
<div v-if="disabled" class="disabled__wrap" @click="disabledClick">
<div>
<i class="el-icon-refresh-right"></i>
<div>{{ disabledText }}</div>
</div>
</div>
</div>
</template>
<script setup lang="ts" name="Qrcode">
import { PropType, nextTick, ref, watch, computed, unref } from 'vue'
import type { LogoTypes } from './types'
import QRCode from 'qrcode'
import { QRCodeRenderersOptions } from 'qrcode'
import { deepClone } from '@/utils'
import { isString } from '@/utils/validate'
const { toCanvas, toDataURL } = QRCode
const props = defineProps({
// img 或者 canvas,img不支持logo嵌套
tag: {
type: String as PropType<'canvas' | 'img'>,
default: 'canvas',
validator: (v: string) => ['canvas', 'img'].includes(v)
},
// 二维码内容
text: {
type: [String, Array] as PropType<string | any[]>,
default: null
},
// qrcode.js配置项
options: {
type: Object as PropType<QRCodeRenderersOptions>,
default: () => {
return {}
}
},
// 宽度
width: {
type: Number as PropType<number>,
default: 200
},
// logo
logo: {
type: [String, Object] as PropType<Partial<LogoTypes> | string>,
default: ''
},
// 是否过期
disabled: {
type: Boolean as PropType<boolean>,
default: false
},
// 过期提示内容
disabledText: {
type: String as PropType<string>,
default: '二维码已失效'
}
})
const emit = defineEmits(['done', 'click', 'disabled-click'])
const loading = ref<boolean>(true)
const wrapRef = ref<HTMLCanvasElement | HTMLImageElement | null>(null)
const renderText = computed(() => String(props.text))
const wrapStyle = computed(() => {
return {
width: props.width + 'px',
height: props.width + 'px'
}
})
watch(
() => renderText.value,
(val) => {
if (!val) return
initQrcode()
},
{
deep: true,
immediate: true
}
)
// 初始化
function initQrcode() {
nextTick(async () => {
const options = deepClone(props.options || {})
if (props.tag === 'canvas') {
// 容错率,默认对内容少的二维码采用高容错率,内容多的二维码采用低容错率
options.errorCorrectionLevel =
options.errorCorrectionLevel || getErrorCorrectionLevel(renderText.value)
getOriginWidth(renderText.value, options).then(async (_width) => {
options.scale = props.width === 0 ? undefined : (props.width / _width) * 4
const canvasRef: any = await toCanvas(unref(wrapRef as any), renderText.value, options)
if (props.logo) {
const url = await createLogoCode(canvasRef)
emit('done', url)
loading.value = false
} else {
emit('done', canvasRef.toDataURL())
loading.value = false
}
})
} else {
const url = await toDataURL(renderText.value, {
errorCorrectionLevel: 'H',
width: props.width,
...options
})
unref(wrapRef as any).src = url
emit('done', url)
loading.value = false
}
})
}
// 生成logo
function createLogoCode(canvasRef: HTMLCanvasElement) {
const canvasWidth = canvasRef.width
const logoOptions: LogoTypes = Object.assign(
{
logoSize: 0.15,
bgColor: '#ffffff',
borderSize: 0.05,
crossOrigin: 'anonymous',
borderRadius: 8,
logoRadius: 0
},
isString(props.logo) ? {} : props.logo
)
const {
logoSize = 0.15,
bgColor = '#ffffff',
borderSize = 0.05,
crossOrigin = 'anonymous',
borderRadius = 8,
logoRadius = 0
} = logoOptions
const logoSrc = isString(props.logo) ? props.logo : props.logo.src
const logoWidth = canvasWidth * logoSize
const logoXY = (canvasWidth * (1 - logoSize)) / 2
const logoBgWidth = canvasWidth * (logoSize + borderSize)
const logoBgXY = (canvasWidth * (1 - logoSize - borderSize)) / 2
const ctx = canvasRef.getContext('2d')
if (!ctx) return
// logo 底色
canvasRoundRect(ctx)(logoBgXY, logoBgXY, logoBgWidth, logoBgWidth, borderRadius)
ctx.fillStyle = bgColor
ctx.fill()
// logo
const image = new Image()
if (crossOrigin || logoRadius) {
image.setAttribute('crossOrigin', crossOrigin)
}
;(image as any).src = logoSrc
// 使用image绘制可以避免某些跨域情况
const drawLogoWithImage = (image: HTMLImageElement) => {
ctx.drawImage(image, logoXY, logoXY, logoWidth, logoWidth)
}
// 使用canvas绘制以获得更多的功能
const drawLogoWithCanvas = (image: HTMLImageElement) => {
const canvasImage = document.createElement('canvas')
canvasImage.width = logoXY + logoWidth
canvasImage.height = logoXY + logoWidth
const imageCanvas = canvasImage.getContext('2d')
if (!imageCanvas || !ctx) return
imageCanvas.drawImage(image, logoXY, logoXY, logoWidth, logoWidth)
canvasRoundRect(ctx)(logoXY, logoXY, logoWidth, logoWidth, logoRadius)
if (!ctx) return
const fillStyle = ctx.createPattern(canvasImage, 'no-repeat')
if (fillStyle) {
ctx.fillStyle = fillStyle
ctx.fill()
}
}
// 将 logo绘制到 canvas上
return new Promise((resolve: any) => {
image.onload = () => {
logoRadius ? drawLogoWithCanvas(image) : drawLogoWithImage(image)
resolve(canvasRef.toDataURL())
}
})
}
// 得到原QrCode的大小以便缩放得到正确的QrCode大小
function getOriginWidth(content: string, options: QRCodeRenderersOptions) {
const _canvas = document.createElement('canvas')
return toCanvas(_canvas, content, options).then(() => _canvas.width)
}
// 对于内容少的QrCode增大容错率
function getErrorCorrectionLevel(content: string) {
if (content.length > 36) {
return 'M'
} else if (content.length > 16) {
return 'Q'
} else {
return 'H'
}
}
// 点击二维码
function clickCode() {
emit('click')
}
// 失效点击事件
function disabledClick() {
emit('disabled-click')
}
// copy来的方法用于绘制圆角
function canvasRoundRect(ctx: CanvasRenderingContext2D) {
return (x: number, y: number, w: number, h: number, r: number) => {
const minSize = Math.min(w, h)
if (r > minSize / 2) {
r = minSize / 2
}
ctx.beginPath()
ctx.moveTo(x + r, y)
ctx.arcTo(x + w, y, x + w, y + h, r)
ctx.arcTo(x + w, y + h, x, y + h, r)
ctx.arcTo(x, y + h, x, y, r)
ctx.arcTo(x, y, x + w, y, r)
ctx.closePath()
return ctx
}
}
</script>
<style lang="less" scoped>
.qrcode__wrap {
position: relative;
display: inline-block;
.disabled__wrap {
position: absolute;
top: 0;
left: 0;
display: flex;
width: 100%;
height: 100%;
cursor: pointer;
background: rgba(255, 255, 255, 0.95);
align-items: center;
justify-content: center;
& > div {
position: absolute;
top: 50%;
left: 50%;
font-weight: bold;
transform: translate(-50%, -50%);
i {
margin-bottom: 10px;
font-size: 30px;
}
}
}
}
</style>

View File

@@ -1,9 +0,0 @@
export interface LogoTypes {
src?: string
logoSize?: number
bgColor?: string
borderSize?: number
crossOrigin?: string
borderRadius?: number
logoRadius?: number
}

View File

@@ -1,71 +0,0 @@
import Clipboard from 'clipboard'
import { Directive, DirectiveBinding } from 'vue'
import { Message } from '_c/Message'
if (!Clipboard) {
throw new Error('you should npm install `clipboard` --save at first ')
}
export const clipboard: Directive = {
beforeMount(el: HTMLElement, binding: DirectiveBinding) {
createdClipboard(el, binding.arg, binding.value)
},
updated(el: HTMLElement | any, binding: DirectiveBinding) {
if (binding.arg === 'success') {
el._v_clipboard_success = binding.value
} else if (binding.arg === 'error') {
el._v_clipboard_error = binding.value
} else {
el._v_clipboard.text = function () {
return binding.value
}
el._v_clipboard.action = function () {
return 'copy'
}
}
},
unmounted(el: HTMLElement | any, binding: DirectiveBinding) {
if (binding.arg === 'success') {
delete el._v_clipboard_success
} else if (binding.arg === 'error') {
delete el._v_clipboard_error
} else {
el._v_clipboard.destroy()
delete el._v_clipboard
}
}
}
function createdClipboard(el: HTMLElement | any, arg: string | undefined, value: any) {
if (arg === 'success') {
el._v_clipboard_success = value
} else if (arg === 'error') {
el._v_clipboard_error = value
} else {
const clipboard = new Clipboard(el, {
text() {
return value
},
action() {
return 'copy'
}
})
clipboard.on('success', (e) => {
const callback = el._v_clipboard_success
if (callback) {
callback(e)
} else {
Message.success('复制成功')
}
})
clipboard.on('error', (e) => {
const callback = el._v_clipboard_error
if (callback) {
callback(e)
} else {
Message.success('复制失败')
}
})
el._v_clipboard = clipboard
}
}

View File

@@ -1,7 +0,0 @@
import type { App } from 'vue'
import { clipboard } from './clipboard'
export function setupDirectives(app: App) {
app.directive('clipboard', clipboard)
}

View File

@@ -1,235 +0,0 @@
import { reactive, ref } from 'vue'
import { ElMessageBox } from 'element-plus'
import { Message } from '_c/Message'
interface DefalutParams {
pageIndex: number // 页码
pageSize: number // 页数
}
interface DelsParmas {
noDataText?: string // 没有选中数据时的提示
text?: string // 删除前的提示
hiddenVerify?: boolean // 是否隐藏前置判断
}
interface InitOption {
listFun?: Fn
delFun?: Fn
}
interface DilogOption {
title?: string
width?: string
}
export function useWork(option?: InitOption) {
const listFun: Nullable<Fn> = option?.listFun || null
const delFun: Nullable<Fn> = option?.delFun || null
// 请求接口的基本参数
const defaultParams = reactive<DefalutParams>({
pageIndex: 1,
pageSize: 10
})
// 多选数据
const selectionData = ref<IObj>([])
// 表格数据
const tableData = ref<IObj>([])
// 表格加载状态
const loading = ref<boolean>(true)
// 表格总条数
const total = ref<number>(0)
// 是否展示弹窗
const dialogVisible = ref<boolean>(false)
// 弹窗标题
const dialogTitle = ref<string>('')
// 组件名称
const comName = ref<string>('')
// 弹窗宽度
const dialogWidth = ref<string>('')
// 传送的数据
const rowData = ref<Nullable<IObj>>(null)
// 需要传给后端的其他参数
const otherParams = ref<Nullable<IObj>>({})
// 表格展示条目改变时候重置基本参数
function sizeChange(val: number) {
loading.value = true
defaultParams.pageIndex = 1
defaultParams.pageSize = val
}
// 表格分页改变时候重置基本参数
function currentChange(val: number) {
loading.value = true
defaultParams.pageIndex = val
}
// 删除多选
/**
* @param {Object} callBack
* @param {Object} config {
noDataText?: string // 没有选中数据时的提示
text?: string // 删除前的提示
hiddenVerify?: boolean // 是否隐藏前置判断
}
*/
function delData(callBack: Fn, config: DelsParmas) {
if (!selectionData.value.length && config && !config.hiddenVerify) {
Message.warning((config && config.noDataText) || '请选择需要删除的数据!')
return
}
ElMessageBox.confirm((config && config.text) || '此操作将永久删除选中数据, 是否继续?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
await callBack()
})
}
// 多选变化的时候
function handleSelectionChange(selection: IObj) {
selectionData.value = selection
}
// 改变弹窗dialogVisible
function toggleVisible(val = false) {
dialogVisible.value = val
}
// 以下都是业务逻辑混入
// 请求数据
async function getList(data?: IObj) {
try {
const res = await (listFun as Fn)({
params: Object.assign(otherParams.value, data || {}, defaultParams)
})
if (res) {
// 返回结果可以自己更改
console.log(res)
total.value = res.data.total
tableData.value = res.data.list
}
} finally {
loading.value = false
}
}
// 查询
function searchSubmit(data: IObj) {
// 该方法重置了一些默认参数
currentChange(1)
getList(data)
}
// 重置
function resetSubmit(data: IObj) {
// 该方法重置了一些默认参数
currentChange(1)
getList(data)
}
// 展示多少条
function handleSizeChange(val: number) {
// 该方法重置了一些默认参数
sizeChange(val)
getList()
}
// 展示第几页
function handleCurrentChange(val: number) {
// 该方法重置了一些默认参数
currentChange(val)
getList()
}
// 删除多选
function dels(item: IObj, single?: boolean, callback?: Fn) {
delData(
async () => {
let ids: Nullable<IObj[]> = null
if (item.id) {
ids = single ? item.id : [item.id]
} else {
ids = selectionData.value.map((v: IObj) => {
return v.id
})
}
const res = await (delFun as Fn)({
data: { ids }
})
if (res) {
Message.success('删除成功')
const pageIndex =
total.value % defaultParams.pageSize === (ids as IObj[]).length ||
defaultParams.pageSize === 1
? defaultParams.pageIndex > 1
? defaultParams.pageIndex - 1
: defaultParams.pageIndex
: defaultParams.pageIndex
currentChange(pageIndex)
getList()
callback && callback instanceof Function && callback()
}
},
{ hiddenVerify: item.id }
)
}
// 打开弹窗
function open(row: Nullable<IObj>, component: string, options?: DilogOption) {
comName.value = component
dialogTitle.value =
(options && options.title) || (!row ? '新增' : component === 'Detail' ? '详情' : '编辑')
dialogWidth.value = (options && options.width) || '55%'
rowData.value = row || null
toggleVisible(true)
}
// 成功之后的回调
function refreshTable(type: string) {
if (type === 'add') {
currentChange(1)
}
toggleVisible()
getList()
}
return {
defaultParams,
selectionData,
tableData,
loading,
total,
dialogVisible,
dialogTitle,
comName,
dialogWidth,
rowData,
otherParams,
// delData,
handleSelectionChange,
toggleVisible,
getList,
searchSubmit,
resetSubmit,
handleSizeChange,
handleCurrentChange,
dels,
open,
refreshTable
}
}

View File

@@ -6,31 +6,20 @@ import router, { setupRouter } from './router' // 路由
import { setupStore } from './store' // 状态管理
import { setupDirectives } from '@/directives' // 自定义指令
import { setupGlobCom } from './components'
// import { setupElement } from '@/plugins/element-plus'
import '@/styles/index.less'
import 'virtual:svg-icons-register'
import './permission' // permission control
import { mockXHR } from '@/mock'
mockXHR()
const app = createApp(App)
setupStore(app) // 引入状态管理
setupRouter(app) // 引入路由
setupDirectives(app)
// setupElement(app)
setupGlobCom(app) // 引入全局组件
router.isReady().then(() => {

View File

@@ -1,131 +0,0 @@
import Mock from 'mockjs'
import { toAnyString } from '@/utils'
let List: any[] = []
const count = 100
const baseContent =
'<p>I am testing data, I am testing data.</p><p><img src="https://wpimg.wallstcn.com/4c69009c-0fd4-4153-b112-6cb53d1cf943"></p>'
for (let i = 0; i < count; i++) {
List.push(
Mock.mock({
id: toAnyString(),
// timestamp: +Mock.Random.date('T'),
author: '@first',
title: '@title(5, 10)',
content: baseContent,
importance: '@integer(1, 3)',
display_time: '@datetime',
pageviews: '@integer(300, 5000)'
// image_uri
})
)
}
export default [
// 列表接口
{
url: 'http://mockjs.test.cn/example/list',
type: 'get',
response: (config: any) => {
const { title, pageIndex, pageSize } = config.query
const mockList = List.filter((item) => {
if (title && item.title.indexOf(title) < 0) return false
return true
})
const pageList = mockList.filter(
(_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1)
)
return {
code: '0000',
data: {
total: mockList.length,
list: pageList
}
}
}
},
// 删除接口
{
url: 'http://mockjs.test.cn/example/delete',
type: 'post',
response: (config: any) => {
const ids = config.body.ids
if (!ids) {
return {
code: '500',
message: '请选择需要删除的数据'
}
} else {
let i = List.length
while (i--) {
if (ids.indexOf(List[i].id) !== -1) {
List.splice(i, 1)
}
}
return {
code: '0000',
data: 'success'
}
}
}
},
// 详情接口
{
url: 'http://mockjs.test.cn/example/detail',
type: 'get',
response: (config: any) => {
const { id } = config.query
for (const example of List) {
if (example.id === id) {
return {
code: '0000',
data: example
}
}
}
}
},
// 保存接口
{
url: 'http://mockjs.test.cn/example/save',
type: 'post',
response: (config: any) => {
const data = config.body
if (!data.id) {
List = [
Object.assign(data, {
id: toAnyString(),
importance: Number(data.importance)
})
].concat(List)
return {
code: '0000',
data: 'success'
}
} else {
List.map((item) => {
if (item.id === data.id) {
for (const key in item) {
if (key === 'importance') {
item[key] = Number(data[key])
} else {
item[key] = data[key]
}
}
}
})
return {
code: '0000',
data: 'success'
}
}
}
}
]

View File

@@ -1,64 +0,0 @@
import Mock from 'mockjs'
import { param2Obj } from '@/utils'
import example from './example'
import user from './user'
import role from './role'
const mocks = [...example, ...user, ...role]
// for front mock
// please use it cautiously, it will redefine XMLHttpRequest,
// which will cause many of your third-party libraries to be invalidated(like progress event).
export function mockXHR() {
const MockJs: any = Mock
MockJs.XHR.prototype.proxy_send = MockJs.XHR.prototype.send
MockJs.XHR.prototype.send = function () {
if (this.custom.xhr) {
this.custom.xhr.withCredentials = this.withCredentials || false
if (this.responseType) {
this.custom.xhr.responseType = this.responseType
}
}
/* eslint-disable */
this.proxy_send(...arguments)
}
function XHR2ExpressReqWrap(respond: any) {
return function (options: any) {
let result = null
if (respond instanceof Function) {
const { body, type, url } = options
// https://expressjs.com/en/4x/api.html#req
result = respond({
method: type,
body: JSON.parse(body),
query: param2Obj(url)
})
} else {
result = respond
}
return Mock.mock(result)
}
}
for (const i of mocks) {
Mock.mock(new RegExp(i.url), i.type || 'get', XHR2ExpressReqWrap(i.response))
}
}
// for mock server
const responseFake = (url: string, type: string, respond: any) => {
return {
url: new RegExp(`${url}`),
type: type || 'get',
response(req: any, res: any) {
res.json(Mock.mock(respond instanceof Function ? respond(req, res) : respond))
}
}
}
export default mocks.map((route) => {
return responseFake(route.url, route.type, route.response)
})

View File

@@ -1,606 +0,0 @@
export const checkedNodes = [
{
path: '/components-demo',
title: '功能组件',
name: 'ComponentsDemo',
children: [
{
path: '/components-demo/echarts',
title: '图表',
name: 'EchartsDemo'
},
{
path: '/components-demo/preview',
title: '图片预览',
name: 'PreviewDemo'
},
{
path: '/components-demo/button',
title: '按钮',
name: 'ButtonDemo'
},
{
path: '/components-demo/message',
title: '消息提示',
name: 'MessageDemo'
},
{
path: '/components-demo/count-to',
title: '数字动画',
name: 'CountToDemo'
},
{
path: '/components-demo/search',
title: '查询',
name: 'SearchDemo'
},
{
path: '/components-demo/editor',
title: '富文本编辑器',
name: 'EditorDemo'
},
{
path: '/components-demo/markdown',
title: 'markdown编辑器',
name: 'MarkdownDemo'
},
{
path: '/components-demo/dialog',
title: '弹窗',
name: 'DialogDemo'
},
{
path: '/components-demo/detail',
title: '详情',
name: 'DetailDemo'
},
{
path: '/components-demo/qrcode',
title: '二维码',
name: 'QrcodeDemo'
},
{
path: '/components-demo/avatars',
title: '头像组',
name: 'AvatarsDemo'
},
{
path: '/components-demo/highlight',
title: '文字高亮',
name: 'HighlightDemo'
},
{
path: '/components-demo/watermark',
name: 'WatermarkDemo',
title: '水印'
}
]
},
{
path: '/components-demo/echarts',
title: '图表',
name: 'EchartsDemo'
},
{
path: '/components-demo/preview',
title: '图片预览',
name: 'PreviewDemo'
},
{
path: '/components-demo/button',
title: '按钮',
name: 'ButtonDemo'
},
{
path: '/components-demo/message',
title: '消息提示',
name: 'MessageDemo'
},
{
path: '/components-demo/count-to',
title: '数字动画',
name: 'CountToDemo'
},
{
path: '/components-demo/search',
title: '查询',
name: 'SearchDemo'
},
{
path: '/components-demo/editor',
title: '富文本编辑器',
name: 'EditorDemo'
},
{
path: '/components-demo/markdown',
title: 'markdown编辑器',
name: 'MarkdownDemo'
},
{
path: '/components-demo/dialog',
title: '弹窗',
name: 'DialogDemo'
},
{
path: '/components-demo/detail',
title: '详情',
name: 'DetailDemo'
},
{
path: '/components-demo/qrcode',
title: '二维码',
name: 'QrcodeDemo'
},
{
path: '/components-demo/avatars',
title: '头像组',
name: 'AvatarsDemo'
},
{
path: '/components-demo/highlight',
title: '文字高亮',
name: 'HighlightDemo'
},
{
path: '/components-demo/watermark',
name: 'WatermarkDemo',
title: '水印'
},
{
path: '/table-demo',
title: '表格',
name: 'TableDemo',
children: [
{
path: '/table-demo/basic-table',
title: '基础表格',
name: 'BasicTable'
},
{
path: '/table-demo/page-table',
title: '分页表格',
name: 'PageTable'
},
{
path: '/table-demo/stripe-table',
title: '带斑马纹表格',
name: 'StripeTable'
},
{
path: '/table-demo/border-table',
title: '带边框表格',
name: 'BorderTable'
},
{
path: '/table-demo/state-table',
title: '带状态表格',
name: 'StateTable'
},
{
path: '/table-demo/fixed-header',
title: '固定表头',
name: 'FixedHeader'
},
{
path: '/table-demo/fixed-column',
title: '固定列',
name: 'FixedColumn'
},
{
path: '/table-demo/fixed-column-header',
title: '固定列和表头',
name: 'FixedColumnHeader'
},
{
path: '/table-demo/fluid-height',
title: '流体高度',
name: 'FluidHeight'
},
{
path: '/table-demo/multi-header',
title: '多级表头',
name: 'MultiHeader'
},
{
path: '/table-demo/single-choice',
title: '单选',
name: 'SingleChoice'
},
{
path: '/table-demo/multiple-choice',
title: '多选',
name: 'MultipleChoice'
},
{
path: '/table-demo/sort-table',
title: '排序',
name: 'SortTable'
},
{
path: '/table-demo/screen-table',
title: '筛选',
name: 'ScreenTable'
},
{
path: '/table-demo/expand-row',
title: '展开行',
name: 'ExpandRow'
},
{
path: '/table-demo/tree-and-load',
title: '树形数据与懒加载',
name: 'TreeAndLoad'
},
{
path: '/table-demo/custom-header',
title: '自定义表头',
name: 'CustomHeader'
},
{
path: '/table-demo/total-table',
title: '表尾合计行',
name: 'TotalTable'
},
{
path: '/table-demo/merge-table',
title: '合并行或列',
name: 'MergeTable'
},
{
path: '/table-demo/custom-index',
title: '自定义索引',
name: 'CustomIndex'
}
]
},
{
path: '/table-demo/basic-table',
title: '基础表格',
name: 'BasicTable'
},
{
path: '/table-demo/page-table',
title: '分页表格',
name: 'PageTable'
},
{
path: '/table-demo/stripe-table',
title: '带斑马纹表格',
name: 'StripeTable'
},
{
path: '/table-demo/border-table',
title: '带边框表格',
name: 'BorderTable'
},
{
path: '/table-demo/state-table',
title: '带状态表格',
name: 'StateTable'
},
{
path: '/table-demo/fixed-header',
title: '固定表头',
name: 'FixedHeader'
},
{
path: '/table-demo/fixed-column',
title: '固定列',
name: 'FixedColumn'
},
{
path: '/table-demo/fixed-column-header',
title: '固定列和表头',
name: 'FixedColumnHeader'
},
{
path: '/table-demo/fluid-height',
title: '流体高度',
name: 'FluidHeight'
},
{
path: '/table-demo/multi-header',
title: '多级表头',
name: 'MultiHeader'
},
{
path: '/table-demo/single-choice',
title: '单选',
name: 'SingleChoice'
},
{
path: '/table-demo/multiple-choice',
title: '多选',
name: 'MultipleChoice'
},
{
path: '/table-demo/sort-table',
title: '排序',
name: 'SortTable'
},
{
path: '/table-demo/screen-table',
title: '筛选',
name: 'ScreenTable'
},
{
path: '/table-demo/expand-row',
title: '展开行',
name: 'ExpandRow'
},
{
path: '/table-demo/tree-and-load',
title: '树形数据与懒加载',
name: 'TreeAndLoad'
},
{
path: '/table-demo/custom-header',
title: '自定义表头',
name: 'CustomHeader'
},
{
path: '/table-demo/total-table',
title: '表尾合计行',
name: 'TotalTable'
},
{
path: '/table-demo/merge-table',
title: '合并行或列',
name: 'MergeTable'
},
{
path: '/table-demo/custom-index',
title: '自定义索引',
name: 'CustomIndex'
},
{
path: '/directives-demo',
title: '自定义指令',
name: 'DirectivesDemo',
children: [
{
path: '/directives-demo/clipboard',
title: 'Clipboard',
name: 'ClipboardDemo'
}
]
},
{
path: '/directives-demo/clipboard',
title: 'Clipboard',
name: 'ClipboardDemo'
},
{
path: '/icon/index',
title: '图标',
name: 'Icons'
},
{
path: '/level',
title: '多级菜单缓存',
name: 'Level',
children: [
{
path: '/level/menu1',
title: 'Menu1',
name: 'Menu1Demo',
children: [
{
path: '/level/menu1/menu1-1',
title: 'Menu1-1',
name: 'Menu11Demo',
children: [
{
path: '/level/menu1/menu1-1/menu1-1-1',
title: 'Menu1-1-1',
name: 'Menu111Demo'
}
]
},
{
path: '/level/menu1/menu1-2',
title: 'Menu1-2',
name: 'Menu12Demo'
}
]
},
{
path: '/level/menu2',
title: 'Menu2',
name: 'Menu2Demo'
}
]
},
{
path: '/level/menu1',
title: 'Menu1',
name: 'Menu1Demo',
children: [
{
path: '/level/menu1/menu1-1',
title: 'Menu1-1',
name: 'Menu11Demo',
children: [
{
path: '/level/menu1/menu1-1/menu1-1-1',
title: 'Menu1-1-1',
name: 'Menu111Demo'
}
]
},
{
path: '/level/menu1/menu1-2',
title: 'Menu1-2',
name: 'Menu12Demo'
}
]
},
{
path: '/level/menu1/menu1-1',
title: 'Menu1-1',
name: 'Menu11Demo',
children: [
{
path: '/level/menu1/menu1-1/menu1-1-1',
title: 'Menu1-1-1',
name: 'Menu111Demo'
}
]
},
{
path: '/level/menu1/menu1-1/menu1-1-1',
title: 'Menu1-1-1',
name: 'Menu111Demo'
},
{
path: '/level/menu1/menu1-2',
title: 'Menu1-2',
name: 'Menu12Demo'
},
{
path: '/level/menu2',
title: 'Menu2',
name: 'Menu2Demo'
},
{
path: '/example-demo',
title: '综合实例',
name: 'ExampleDemo',
children: [
{
path: '/example-demo/example-dialog',
title: '列表综合实例-弹窗',
name: 'ExampleDialog'
},
{
path: '/example-demo/example-page',
title: '列表综合实例-页面',
name: 'ExamplePage'
},
{
path: '/example-demo/example-add',
title: '列表综合实例-新增',
name: 'ExampleAdd'
},
{
path: '/example-demo/example-edit',
title: '列表综合实例-编辑',
name: 'ExampleEdit'
},
{
path: '/example-demo/example-detail',
title: '列表综合实例-详情',
name: 'ExampleDetail'
}
]
},
{
path: '/example-demo/example-dialog',
title: '列表综合实例-弹窗',
name: 'ExampleDialog'
},
{
path: '/example-demo/example-page',
title: '列表综合实例-页面',
name: 'ExamplePage'
},
{
path: '/example-demo/example-add',
title: '列表综合实例-新增',
name: 'ExampleAdd'
},
{
path: '/example-demo/example-edit',
title: '列表综合实例-编辑',
name: 'ExampleEdit'
},
{
path: '/example-demo/example-detail',
title: '列表综合实例-详情',
name: 'ExampleDetail'
},
{
path: '/role-demo',
title: '权限管理',
name: 'RoleDemo',
children: [
{
path: '/role-demo/user',
title: '用户管理',
name: 'User'
},
{
path: '/role-demo/role',
title: '角色管理',
name: 'Role'
}
]
},
{
path: '/role-demo/user',
title: '用户管理',
name: 'User'
},
{
path: '/role-demo/role',
title: '角色管理',
name: 'Role'
}
]
export const checkedkeys = [
'/components-demo',
'/components-demo/echarts',
'/components-demo/preview',
'/components-demo/button',
'/components-demo/message',
'/components-demo/count-to',
'/components-demo/search',
'/components-demo/editor',
'/components-demo/markdown',
'/components-demo/dialog',
'/components-demo/more',
'/components-demo/detail',
'/components-demo/qrcode',
'/components-demo/avatars',
'/components-demo/watermark',
'/table-demo',
'/table-demo/basic-table',
'/table-demo/page-table',
'/table-demo/stripe-table',
'/table-demo/border-table',
'/table-demo/state-table',
'/table-demo/fixed-header',
'/table-demo/fixed-column',
'/table-demo/fixed-column-header',
'/table-demo/fluid-height',
'/table-demo/multi-header',
'/table-demo/single-choice',
'/table-demo/multiple-choice',
'/table-demo/sort-table',
'/table-demo/screen-table',
'/table-demo/expand-row',
'/table-demo/tree-and-load',
'/table-demo/custom-header',
'/table-demo/total-table',
'/table-demo/merge-table',
'/table-demo/custom-index',
'/directives-demo',
'/directives-demo/clipboard',
'/icon/index',
'/level',
'/level/menu1',
'/level/menu1/menu1-1',
'/level/menu1/menu1-1/menu1-1-1',
'/level/menu1/menu1-2',
'/level/menu2',
'/example-demo',
'/example-demo/example-dialog',
'/example-demo/example-page',
'/example-demo/example-add',
'/example-demo/example-edit',
'/example-demo/example-detail',
'/role-demo',
'/role-demo/user',
'/role-demo/role'
]

View File

@@ -1,100 +0,0 @@
import { useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache()
import { checkedNodes, checkedkeys } from './admin-role'
import { checkedRoleNodes } from './test-role'
let List: {
roleName: string
id: string
checkedNodes: any[]
checkedkeys: any[]
}[] = wsCache.get('roleList') || [
{
roleName: 'admin',
id: '1',
checkedNodes: checkedNodes,
checkedkeys: checkedkeys
},
{
roleName: 'test',
id: '2',
checkedNodes: checkedRoleNodes,
checkedkeys: []
}
]
export default [
// 列表接口
{
url: 'http://mockjs.test.cn/role/list',
type: 'get',
response: (config: any) => {
const { roleName, pageIndex, pageSize } = config.query
const mockList = List.filter((item) => {
if (roleName && item.roleName.indexOf(roleName) < 0) return false
return true
})
const pageList = mockList.filter(
(_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1)
)
return {
code: '0000',
data: {
total: mockList.length,
list: pageList
}
}
}
},
// 详情接口
{
url: 'http://mockjs.test.cn/role/detail',
type: 'get',
response: (config: any) => {
const { id } = config.query
for (const role of List) {
if (role.id === id) {
return {
code: '0000',
data: role
}
}
}
}
},
// 保存接口
{
url: 'http://mockjs.test.cn/role/save',
type: 'post',
response: (config: any) => {
const data = config.body
if (!data.id) {
List = [data].concat(List)
return {
code: '0000',
data: 'success'
}
} else {
List.map((item) => {
if (item.id === data.id) {
for (const key in item) {
item[key] = data[key]
}
}
})
// 存在缓存中,避免刷新没有掉
wsCache.set('roleList', List)
return {
code: '0000',
data: 'success'
}
}
}
}
]

View File

@@ -1,460 +0,0 @@
export const checkedRoleNodes = [
{
path: '/components-demo',
component: '#',
redirect: '/components-demo/echarts',
name: 'ComponentsDemo',
meta: {
title: '功能组件',
icon: 'component',
alwaysShow: true
},
children: [
{
path: 'echarts',
component: 'views/components-demo/echarts/index',
name: 'EchartsDemo',
meta: {
title: '图表'
}
},
{
path: 'preview',
component: 'views/components-demo/preview/index',
name: 'PreviewDemo',
meta: {
title: '图片预览'
}
},
{
path: 'message',
component: 'views/components-demo/message/index',
name: 'MessageDemo',
meta: {
title: '消息提示'
}
},
{
path: 'search',
component: 'views/components-demo/search/index',
name: 'SearchDemo',
meta: {
title: '查询'
}
},
{
path: 'editor',
component: 'views/components-demo/editor/index',
name: 'EditorDemo',
meta: {
title: '富文本编辑器'
}
},
// {
// path: 'markdown',
// component: 'views/components-demo/markdown/index',
// name: 'MarkdownDemo',
// meta: {
// title: 'markdown编辑器'
// }
// },
{
path: 'dialog',
component: 'views/components-demo/dialog/index',
name: 'DialogDemo',
meta: {
title: '弹窗'
}
},
{
path: 'detail',
component: 'views/components-demo/detail/index',
name: 'DetailDemo',
meta: {
title: '详情'
}
},
{
path: 'qrcode',
component: 'views/components-demo/qrcode/index',
name: 'QrcodeDemo',
meta: {
title: '二维码'
}
},
{
path: 'avatars',
component: 'views/components-demo/avatars/index',
name: 'AvatarsDemo',
meta: {
title: '头像组'
}
},
{
path: 'highlight',
component: 'views/components-demo/highlight/index',
name: 'HighlightDemo',
meta: {
title: '文字高亮'
}
},
{
path: 'watermark',
component: 'views/components-demo/watermark/index',
name: 'WatermarkDemo',
meta: {
title: '水印'
}
}
]
},
{
path: '/table-demo',
component: '#',
redirect: '/table-demo/basic-usage',
name: 'TableDemo',
meta: {
title: '表格',
icon: 'table',
alwaysShow: true
},
children: [
{
path: 'basic-table',
component: 'views/table-demo/basic-table/index',
name: 'BasicTable',
meta: {
title: '基础表格'
}
},
{
path: 'page-table',
component: 'views/table-demo/page-table/index',
name: 'PageTable',
meta: {
title: '分页表格'
}
},
{
path: 'stripe-table',
component: 'views/table-demo/stripe-table/index',
name: 'StripeTable',
meta: {
title: '带斑马纹表格'
}
},
{
path: 'border-table',
component: 'views/table-demo/border-table/index',
name: 'BorderTable',
meta: {
title: '带边框表格'
}
},
{
path: 'state-table',
component: 'views/table-demo/state-table/index',
name: 'StateTable',
meta: {
title: '带状态表格'
}
},
{
path: 'fixed-header',
component: 'views/table-demo/fixed-header/index',
name: 'FixedHeader',
meta: {
title: '固定表头'
}
},
{
path: 'fixed-column',
component: 'views/table-demo/fixed-column/index',
name: 'FixedColumn',
meta: {
title: '固定列'
}
},
{
path: 'fixed-column-header',
component: 'views/table-demo/fixed-column-header/index',
name: 'FixedColumnHeader',
meta: {
title: '固定列和表头'
}
},
{
path: 'fluid-height',
component: 'views/table-demo/fluid-height/index',
name: 'FluidHeight',
meta: {
title: '流体高度'
}
},
{
path: 'multi-header',
component: 'views/table-demo/multi-header/index',
name: 'MultiHeader',
meta: {
title: '多级表头'
}
},
{
path: 'single-choice',
component: 'views/table-demo/single-choice/index',
name: 'SingleChoice',
meta: {
title: '单选'
}
},
{
path: 'multiple-choice',
component: 'views/table-demo/multiple-choice/index',
name: 'MultipleChoice',
meta: {
title: '多选'
}
},
{
path: 'sort-table',
component: 'views/table-demo/sort-table/index',
name: 'SortTable',
meta: {
title: '排序'
}
},
{
path: 'screen-table',
component: 'views/table-demo/screen-table/index',
name: 'ScreenTable',
meta: {
title: '筛选'
}
},
{
path: 'expand-row',
component: 'views/table-demo/expand-row/index',
name: 'ExpandRow',
meta: {
title: '展开行'
}
},
{
path: 'tree-and-load',
component: 'views/table-demo/tree-and-load/index',
name: 'TreeAndLoad',
meta: {
title: '树形数据与懒加载'
}
},
{
path: 'custom-header',
component: 'views/table-demo/custom-header/index',
name: 'CustomHeader',
meta: {
title: '自定义表头'
}
},
{
path: 'total-table',
component: 'views/table-demo/total-table/index',
name: 'TotalTable',
meta: {
title: '表尾合计行'
}
},
{
path: 'merge-table',
component: 'views/table-demo/merge-table/index',
name: 'MergeTable',
meta: {
title: '合并行或列'
}
},
{
path: 'custom-index',
component: 'views/table-demo/custom-index/index',
name: 'CustomIndex',
meta: {
title: '自定义索引'
}
}
]
},
{
path: '/icon',
component: '#',
name: 'IconsDemo',
meta: {
title: '图标',
icon: 'icon'
},
children: [
{
path: 'index',
component: 'views/icons/index',
name: 'Icons',
meta: {
title: '图标',
icon: 'icon'
}
}
]
},
{
path: '/level',
component: '#',
redirect: '/level/menu1/menu1-1/menu1-1-1',
name: 'Level',
meta: {
title: '多级菜单缓存',
icon: 'nested'
},
children: [
{
path: 'menu1',
name: 'Menu1Demo',
component: '##Menu1Demo',
redirect: '/level/menu1/menu1-1/menu1-1-1',
meta: {
title: 'Menu1'
},
children: [
{
path: 'menu1-1',
name: 'Menu11Demo',
component: '##Menu11Demo',
redirect: '/level/menu1/menu1-1/menu1-1-1',
meta: {
title: 'Menu1-1',
alwaysShow: true
},
children: [
{
path: 'menu1-1-1',
name: 'Menu111Demo',
component: 'views/level/menu111',
meta: {
title: 'Menu1-1-1'
}
}
]
},
{
path: 'menu1-2',
name: 'Menu12Demo',
component: 'views/level/menu12',
meta: {
title: 'Menu1-2'
}
}
]
},
{
path: 'menu2',
name: 'Menu2Demo',
component: 'views/level/menu2',
meta: {
title: 'Menu2'
}
}
]
},
{
path: '/example-demo',
component: '#',
name: 'ExampleDemo',
redirect: '/example-demo/example-dialog',
meta: {
alwaysShow: true,
icon: 'example',
title: '综合实例'
},
children: [
{
path: 'example-dialog',
component: 'views/example-demo/example-dialog/index',
name: 'ExampleDialog',
meta: {
title: '列表综合实例-弹窗'
}
},
{
path: 'example-page',
component: 'views/example-demo/example-page/index',
name: 'ExamplePage',
meta: {
title: '列表综合实例-页面'
}
},
{
path: 'example-add',
component: 'views/example-demo/example-page/example-add',
name: 'ExampleAdd',
meta: {
title: '列表综合实例-新增',
noTagsView: true,
noCache: true,
hidden: true,
showMainRoute: true,
activeMenu: '/example-demo/example-page'
}
},
{
path: 'example-edit',
component: 'views/example-demo/example-page/example-edit',
name: 'ExampleEdit',
meta: {
title: '列表综合实例-编辑',
noTagsView: true,
noCache: true,
hidden: true,
showMainRoute: true,
activeMenu: '/example-demo/example-page'
}
},
{
path: 'example-detail',
component: 'views/example-demo/example-page/example-detail',
name: 'ExampleDetail',
meta: {
title: '列表综合实例-详情',
noTagsView: true,
noCache: true,
hidden: true,
showMainRoute: true,
activeMenu: '/example-demo/example-page'
}
}
]
},
{
path: '/role-demo',
component: '#',
redirect: '/role-demo/user',
name: 'RoleDemo',
meta: {
title: '权限管理',
icon: 'user',
alwaysShow: true
},
children: [
{
path: 'user',
component: 'views/role-demo/user/index',
name: 'User',
meta: {
title: '用户管理'
}
},
{
path: 'role',
component: 'views/role-demo/role/index',
name: 'Role',
meta: {
title: '角色管理'
}
}
]
}
]

View File

@@ -1,71 +0,0 @@
const List: {
userName: string
password: string
role: string
roleId: string
}[] = [
{
userName: 'admin',
password: 'admin',
role: 'admin',
roleId: '1'
},
{
userName: 'test',
password: 'test',
role: 'test',
roleId: '2'
}
]
export default [
// 列表接口
{
url: 'http://mockjs.test.cn/user/list',
type: 'get',
response: (config: any) => {
const { userName, pageIndex, pageSize } = config.query
const mockList = List.filter((item) => {
if (userName && item.userName.indexOf(userName) < 0) return false
return true
})
const pageList = mockList.filter(
(_, index) => index < pageSize * pageIndex && index >= pageSize * (pageIndex - 1)
)
return {
code: '0000',
data: {
total: mockList.length,
list: pageList
}
}
}
},
// 登录接口
{
url: 'http://mockjs.test.cn/user/login',
type: 'post',
response: (config: any) => {
const data = config.body
let hasUser = false
for (const user of List) {
if (user.userName === data.userName && user.password === data.passWord) {
hasUser = true
return {
code: '0000',
data: user
}
}
}
if (!hasUser) {
return {
code: '500',
message: '账号或密码错误'
}
}
}
}
]

View File

@@ -97,347 +97,10 @@ export const constantRouterMap: AppRouteRecordRaw[] = [
meta: { title: '文档', icon: 'documentation' }
}
]
},
{
path: '/guide',
component: Layout,
name: 'Guide',
meta: {},
children: [
{
path: 'index',
component: () => import('_v/guide/index.vue'),
name: 'GuideDemo',
meta: {
title: '引导页',
icon: 'guide'
}
}
]
}
]
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('_v/components-demo/echarts/index.vue'),
name: 'EchartsDemo',
meta: {
title: '图表'
}
},
{
path: 'preview',
component: () => import('_v/components-demo/preview/index.vue'),
name: 'PreviewDemo',
meta: {
title: '图片预览'
}
},
{
path: 'message',
component: () => import('_v/components-demo/message/index.vue'),
name: 'MessageDemo',
meta: {
title: '消息提示'
}
},
{
path: 'count-to',
component: () => import('_v/components-demo/count-to/index.vue'),
name: 'CountToDemo',
meta: {
title: '数字动画'
}
},
{
path: 'search',
component: () => import('_v/components-demo/search/index.vue'),
name: 'SearchDemo',
meta: {
title: '查询'
}
},
{
path: 'editor',
component: () => import('_v/components-demo/editor/index.vue'),
name: 'EditorDemo',
meta: {
title: '富文本编辑器'
}
},
{
path: 'dialog',
component: () => import('_v/components-demo/dialog/index.vue'),
name: 'DialogDemo',
meta: {
title: '弹窗'
}
},
{
path: 'detail',
component: () => import('_v/components-demo/detail/index.vue'),
name: 'DetailDemo',
meta: {
title: '详情'
}
},
{
path: 'qrcode',
component: () => import('_v/components-demo/qrcode/index.vue'),
name: 'QrcodeDemo',
meta: {
title: '二维码'
}
},
{
path: 'avatars',
component: () => import('_v/components-demo/avatars/index.vue'),
name: 'AvatarsDemo',
meta: {
title: '头像组'
}
},
{
path: 'highlight',
component: () => import('_v/components-demo/highlight/index.vue'),
name: 'HighlightDemo',
meta: {
title: '文字高亮'
}
},
{
path: 'watermark',
component: () => import('_v/components-demo/watermark/index.vue'),
name: 'WatermarkDemo',
meta: {
title: '水印'
}
}
]
},
{
path: '/table-demo',
component: Layout,
redirect: '/table-demo/basic-table',
name: 'TableDemo',
meta: {
title: '表格',
icon: 'table',
alwaysShow: true
},
children: [
{
path: 'basic-table',
component: () => import('_v/table-demo/basic-table/index.vue'),
name: 'BasicTable',
meta: {
title: '基础表格'
}
},
{
path: 'page-table',
component: () => import('_v/table-demo/page-table/index.vue'),
name: 'PageTable',
meta: {
title: '分页表格'
}
},
{
path: 'stripe-table',
component: () => import('_v/table-demo/stripe-table/index.vue'),
name: 'StripeTable',
meta: {
title: '带斑马纹表格'
}
},
{
path: 'border-table',
component: () => import('_v/table-demo/border-table/index.vue'),
name: 'BorderTable',
meta: {
title: '带边框表格'
}
},
{
path: 'state-table',
component: () => import('_v/table-demo/state-table/index.vue'),
name: 'StateTable',
meta: {
title: '带状态表格'
}
},
{
path: 'fixed-header',
component: () => import('_v/table-demo/fixed-header/index.vue'),
name: 'FixedHeader',
meta: {
title: '固定表头'
}
},
{
path: 'fixed-column',
component: () => import('_v/table-demo/fixed-column/index.vue'),
name: 'FixedColumn',
meta: {
title: '固定列'
}
},
{
path: 'fixed-column-header',
component: () => import('_v/table-demo/fixed-column-header/index.vue'),
name: 'FixedColumnHeader',
meta: {
title: '固定列和表头'
}
},
{
path: 'fluid-height',
component: () => import('_v/table-demo/fluid-height/index.vue'),
name: 'FluidHeight',
meta: {
title: '流体高度'
}
},
{
path: 'multi-header',
component: () => import('_v/table-demo/multi-header/index.vue'),
name: 'MultiHeader',
meta: {
title: '多级表头'
}
},
{
path: 'single-choice',
component: () => import('_v/table-demo/single-choice/index.vue'),
name: 'SingleChoice',
meta: {
title: '单选'
}
},
{
path: 'multiple-choice',
component: () => import('_v/table-demo/multiple-choice/index.vue'),
name: 'MultipleChoice',
meta: {
title: '多选'
}
},
{
path: 'sort-table',
component: () => import('_v/table-demo/sort-table/index.vue'),
name: 'SortTable',
meta: {
title: '排序'
}
},
{
path: 'screen-table',
component: () => import('_v/table-demo/screen-table/index.vue'),
name: 'ScreenTable',
meta: {
title: '筛选'
}
},
{
path: 'expand-row',
component: () => import('_v/table-demo/expand-row/index.vue'),
name: 'ExpandRow',
meta: {
title: '展开行'
}
},
{
path: 'tree-and-load',
component: () => import('_v/table-demo/tree-and-load/index.vue'),
name: 'TreeAndLoad',
meta: {
title: '树形数据与懒加载'
}
},
{
path: 'custom-header',
component: () => import('_v/table-demo/custom-header/index.vue'),
name: 'CustomHeader',
meta: {
title: '自定义表头'
}
},
{
path: 'total-table',
component: () => import('_v/table-demo/total-table/index.vue'),
name: 'TotalTable',
meta: {
title: '表尾合计行'
}
},
{
path: 'merge-table',
component: () => import('_v/table-demo/merge-table/index.vue'),
name: 'MergeTable',
meta: {
title: '合并行或列'
}
},
{
path: 'custom-index',
component: () => import('_v/table-demo/custom-index/index.vue'),
name: 'CustomIndex',
meta: {
title: '自定义索引'
}
}
]
},
{
path: '/directives-demo',
component: Layout,
redirect: '/directives-demo/clipboard',
name: 'DirectivesDemo',
meta: {
title: '自定义指令',
icon: 'clipboard',
alwaysShow: true
},
children: [
{
path: 'clipboard',
component: () => import('_v/directives-demo/clipboard/index.vue'),
name: 'ClipboardDemo',
meta: {
title: 'Clipboard'
}
}
]
},
{
path: '/icon',
component: Layout,
name: 'IconsDemo',
meta: {},
children: [
{
path: 'index',
component: () => import('_v/icons/index.vue'),
name: 'Icons',
meta: {
title: '图标',
icon: 'icon'
}
}
]
},
{
path: '/level',
component: Layout,
@@ -496,103 +159,6 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
}
}
]
},
{
path: '/example-demo',
component: Layout,
name: 'ExampleDemo',
redirect: '/example-demo/example-dialog',
meta: {
alwaysShow: true,
icon: 'example',
title: '综合实例'
},
children: [
{
path: 'example-dialog',
component: () => import('_v/example-demo/example-dialog/index.vue'),
name: 'ExampleDialog',
meta: {
title: '列表综合实例-弹窗'
}
},
{
path: 'example-page',
component: () => import('_v/example-demo/example-page/index.vue'),
name: 'ExamplePage',
meta: {
title: '列表综合实例-页面'
}
},
{
path: 'example-add',
component: () => import('_v/example-demo/example-page/example-add.vue'),
name: 'ExampleAdd',
meta: {
title: '列表综合实例-新增',
noTagsView: true,
noCache: true,
hidden: true,
showMainRoute: true,
activeMenu: '/example-demo/example-page'
}
},
{
path: 'example-edit',
component: () => import('_v/example-demo/example-page/example-edit.vue'),
name: 'ExampleEdit',
meta: {
title: '列表综合实例-编辑',
noTagsView: true,
noCache: true,
hidden: true,
showMainRoute: true,
activeMenu: '/example-demo/example-page'
}
},
{
path: 'example-detail',
component: () => import('_v/example-demo/example-page/example-detail.vue'),
name: 'ExampleDetail',
meta: {
title: '列表综合实例-详情',
noTagsView: true,
noCache: true,
hidden: true,
showMainRoute: true,
activeMenu: '/example-demo/example-page'
}
}
]
},
{
path: '/role-demo',
component: Layout,
redirect: '/role-demo/user',
name: 'RoleDemo',
meta: {
title: '权限管理',
icon: 'user',
alwaysShow: true
},
children: [
{
path: 'user',
component: () => import('_v/role-demo/user/index.vue'),
name: 'User',
meta: {
title: '用户管理'
}
},
{
path: 'role',
component: () => import('_v/role-demo/role/index.vue'),
name: 'Role',
meta: {
title: '角色管理'
}
}
]
}
]

View File

@@ -2,20 +2,20 @@ import { defineStore } from 'pinia'
import { asyncRouterMap, constantRouterMap } from '@/router'
import { deepClone } from '@/utils'
import { AppRouteRecordRaw } from '@/router/types'
import { useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache()
import { isExternal } from '@/utils/validate'
// import { useCache } from '@/hooks/web/useCache'
// const { wsCache } = useCache()
// import { isExternal } from '@/utils/validate'
import path from 'path-browserify'
import { getParentLayout } from '@/router/utils'
// import { getParentLayout } from '@/router/utils'
import { store } from '../index'
import { useAppStoreWithOut } from '@/store/modules/app'
const appStore = useAppStoreWithOut()
// import { useAppStoreWithOut } from '@/store/modules/app'
// const appStore = useAppStoreWithOut()
const modules = import.meta.glob('../../views/**/*.vue')
// const modules = import.meta.glob('../../views/**/*.vue')
/* Layout */
const Layout = () => import('@/layout/index.vue')
// const Layout = () => import('@/layout/index.vue')
export interface PermissionState {
routers: AppRouteRecordRaw[]
@@ -55,15 +55,17 @@ export const usePermissionStore = defineStore({
generateRoutes(): Promise<unknown> {
return new Promise<void>((resolve) => {
// 路由权限控制
let routerMap: AppRouteRecordRaw[] = []
if (wsCache.get(appStore.getUserInfo).roleName === 'admin') {
// 模拟前端控制权限
routerMap = generateRoutesFn(deepClone(asyncRouterMap, ['component']))
} else {
// 模拟后端控制权限
routerMap = getFilterRoutes(wsCache.get(appStore.getUserInfo).checkedNodes)
}
// const routerMap: AppRouteRecordRaw[] = generateRoutesFn(deepClone(asyncRouterMap, ['component']))
// let routerMap: AppRouteRecordRaw[] = []
// if (wsCache.get(appStore.getUserInfo).roleName === 'admin') {
// // 模拟前端控制权限
// routerMap = generateRoutesFn(deepClone(asyncRouterMap, ['component']))
// } else {
// // 模拟后端控制权限
// routerMap = getFilterRoutes(wsCache.get(appStore.getUserInfo).checkedNodes)
// }
const routerMap: AppRouteRecordRaw[] = generateRoutesFn(
deepClone(asyncRouterMap, ['component'])
)
// 动态路由404一定要放到最后面
this.addRouters = routerMap.concat([
{
@@ -100,39 +102,38 @@ function generateRoutesFn(routes: AppRouteRecordRaw[], basePath = '/'): AppRoute
for (const route of routes) {
// skip some route
if (route.meta && route.meta.hidden && !route.meta.showMainRoute) {
console.log(route)
continue
}
let onlyOneChild: Nullable<string> = null
// let onlyOneChild: Nullable<string> = null
if (route.children && route.children.length === 1 && !route.meta.alwaysShow) {
onlyOneChild = (
isExternal(route.children[0].path)
? route.children[0].path
: path.resolve(path.resolve(basePath, route.path), route.children[0].path)
) as string
}
// if (route.children && route.children.length === 1 && !route.meta.alwaysShow) {
// onlyOneChild = (
// isExternal(route.children[0].path)
// ? route.children[0].path
// : path.resolve(path.resolve(basePath, route.path), route.children[0].path)
// ) as string
// }
let data: Nullable<IObj> = null
// 如不需要路由权限,可注释以下逻辑
// 权限过滤,通过获取登录信息里面的角色权限,动态的渲染菜单。
const list = wsCache.get(appStore.getUserInfo).checkedNodes
// 开发者可以根据实际情况进行扩展
for (const item of list) {
// 通过路径去匹配
if (isExternal(item.path) && (onlyOneChild === item.path || route.path === item.path)) {
data = Object.assign({}, route)
} else {
const routePath = path.resolve(basePath, onlyOneChild || route.path)
if (routePath === item.path || (route.meta && route.meta.followRoute === item.path)) {
data = Object.assign({}, route)
}
}
}
// const list = wsCache.get(appStore.getUserInfo).checkedNodes
// // 开发者可以根据实际情况进行扩展
// for (const item of list) {
// // 通过路径去匹配
// if (isExternal(item.path) && (onlyOneChild === item.path || route.path === item.path)) {
// data = Object.assign({}, route)
// } else {
// const routePath = path.resolve(basePath, onlyOneChild || route.path)
// if (routePath === item.path || (route.meta && route.meta.followRoute === item.path)) {
// data = Object.assign({}, route)
// }
// }
// }
// 如不需要路由权限,解注释下面一行
// data = Object.assign({}, route)
data = Object.assign({}, route)
// recursive child routes
if (route.children && data) {
@@ -146,34 +147,34 @@ function generateRoutesFn(routes: AppRouteRecordRaw[], basePath = '/'): AppRoute
}
// 模拟后端过滤路由
function getFilterRoutes(routes: AppRouteRecordRaw[]): AppRouteRecordRaw[] {
const res: AppRouteRecordRaw[] = []
// function getFilterRoutes(routes: AppRouteRecordRaw[]): AppRouteRecordRaw[] {
// const res: AppRouteRecordRaw[] = []
for (const route of routes) {
const data: AppRouteRecordRaw | IObj = {
path: route.path,
name: route.name,
redirect: route.redirect
}
data.meta = Object.assign({}, route.meta || {}, { title: route.meta.title })
if (route.component) {
// 动态加载路由文件,可根据实际情况进行自定义逻辑
data.component = (
(route.component as any) === '#'
? Layout
: (route.component as any).includes('##')
? getParentLayout((route.component as any).split('##')[1])
: modules[`../../${route.component}.vue`]
) as any
}
// recursive child routes
if (route.children) {
data.children = getFilterRoutes(route.children)
}
res.push(data as AppRouteRecordRaw)
}
return res
}
// for (const route of routes) {
// const data: AppRouteRecordRaw | IObj = {
// path: route.path,
// name: route.name,
// redirect: route.redirect
// }
// data.meta = Object.assign({}, route.meta || {}, { title: route.meta.title })
// if (route.component) {
// // 动态加载路由文件,可根据实际情况进行自定义逻辑
// data.component = (
// (route.component as any) === '#'
// ? Layout
// : (route.component as any).includes('##')
// ? getParentLayout((route.component as any).split('##')[1])
// : modules[`../../${route.component}.vue`]
// ) as any
// }
// // recursive child routes
// if (route.children) {
// data.children = getFilterRoutes(route.children)
// }
// res.push(data as AppRouteRecordRaw)
// }
// return res
// }
export function usePermissionStoreWithOut() {
return usePermissionStore(store)

View File

@@ -1,84 +0,0 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="头像组组件 -- 基础用法"
type="info"
style="margin-bottom: 20px"
/>
<avatars :data="data" />
<el-alert
effect="dark"
:closable="false"
title="头像组组件 -- 不显示tooltip"
type="info"
style="margin-top: 20px; margin-bottom: 20px"
/>
<avatars :data="data" :tooltip="false" />
<el-alert
effect="dark"
:closable="false"
title="头像组组件 -- 最多展示5"
type="info"
style="margin-top: 20px; margin-bottom: 20px"
/>
<avatars :data="data" :max="5" />
<el-alert
effect="dark"
:closable="false"
title="头像组组件 -- 展示头像"
type="info"
style="margin-top: 20px; margin-bottom: 20px"
/>
<avatars show-avatar :data="data1" />
</div>
</template>
<script setup lang="ts" name="AvatarsDemo">
import { ref } from 'vue'
import { AvatarConfig } from '_c/Avatars/types'
import Avatars from '_c/Avatars/index.vue'
const data = ref<AvatarConfig[]>([
{ text: '陈某某' },
{ text: '李某某', type: 'success' },
{ text: '张某某', type: 'danger' },
{ text: '王某某', type: 'warning' },
{ text: '龙某某' },
{ text: '孙某某' },
{ text: '刘某某' },
{ text: '赵某某' }
])
const data1 = ref<AvatarConfig[]>([
{
text: '陈某某',
url: 'https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2863969516,2611770076&fm=26&gp=0.jpg'
},
{
text: '李某某',
url: 'https://dss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=465970198,3877503753&fm=26&gp=0.jpg'
},
{
text: '张某某',
url: 'https://dss0.bdstatic.com/70cFvHSh_Q1YnxGkpoWK1HF6hhy/it/u=1857202600,3614139084&fm=26&gp=0.jpg'
},
{
text: '王某某',
url: 'https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=1444080181,4150129244&fm=26&gp=0.jpg'
},
{
text: '龙某某',
url: 'https://dss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2469786567,2163872639&fm=26&gp=0.jpg'
},
{
text: '孙某某',
url: 'https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=4119236579,869456058&fm=26&gp=0.jpg'
},
{ text: '刘某某', url: 'xxxxx' },
{ text: '赵某某' }
])
</script>

View File

@@ -1,116 +0,0 @@
<template>
<div>
<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
ref="countRef"
:start-val="startVal"
:end-val="endVal"
:duration="duration"
:decimals="decimals"
:separator="separator"
:prefix="prefix"
:suffix="suffix"
:autoplay="autoplay"
class="count-to__item"
/>
</div>
<div class="action">
<el-row :gutter="20">
<el-col :span="8">
<div class="action__item">
<span>startVal</span><el-input-number v-model="startVal" :min="0" />
</div>
</el-col>
<el-col :span="8">
<div class="action__item">
<span>endVal</span><el-input-number v-model="endVal" :min="1" />
</div>
</el-col>
<el-col :span="8">
<div class="action__item">
<span>duration</span><el-input-number v-model="duration" :min="1000" />
</div>
</el-col>
<el-col :span="8">
<div class="action__item"> <span>separator</span><el-input v-model="separator" /> </div>
</el-col>
<el-col :span="8">
<div class="action__item"> <span>prefix</span><el-input v-model="prefix" /> </div>
</el-col>
<el-col :span="8">
<div class="action__item"> <span>suffix</span><el-input v-model="suffix" /> </div>
</el-col>
<el-col :span="24">
<div style="margin-top: 20px; text-align: center">
<el-button type="primary" @click="start">start</el-button>
<el-button style="margin-left: 10px" @click="pauseResume">pause/resume</el-button>
</div>
</el-col>
</el-row>
</div>
</div>
</template>
<script setup lang="ts" name="CountToDemo">
import { ref, unref } from 'vue'
import CountTo from '_c/CountTo/index.vue'
const countRef = ref<HTMLElement | null>(null)
const startVal = ref<number>(0)
const endVal = ref<number>(1314512)
const duration = ref<number>(3000)
const decimals = ref<number>(0)
const separator = ref<string>(',')
const prefix = ref<string>('¥ ')
const suffix = ref<string>(' rmb')
const autoplay = ref<boolean>(false)
function start(): void {
;(unref(countRef) as any).start()
}
function pauseResume(): void {
;(unref(countRef) as any).pauseResume()
}
</script>
<style lang="less" scoped>
.count-to {
margin-top: 40px;
text-align: center;
&__item {
font-size: 80px;
font-weight: bold;
color: #f6416c;
}
}
.action {
margin-top: 20px;
&__item {
display: flex;
padding: 0 15px;
margin-bottom: 10px;
align-items: center;
& > span {
display: inline-block;
width: 120px;
text-align: center;
}
:deep(.el-input-number) {
width: 100%;
}
}
}
</style>

View File

@@ -1,218 +0,0 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="详情组件。"
type="info"
style="margin-bottom: 20px"
/>
<com-detail :data="data" :schema="schema" title="基础示例" message="辅助文字" />
<com-detail
title="不可折叠"
:data="data"
:schema="schema"
:collapsed="false"
message="辅助文字"
style="margin-top: 20px"
/>
<com-detail title="没有辅助文字" :data="data" :schema="schema" style="margin-top: 20px" />
<com-detail
title="没有边框"
:data="data"
:schema="schema"
:border="false"
style="margin-top: 20px"
/>
<com-detail
title="垂直布局"
:data="data"
:vertical="true"
:schema="schema"
style="margin-top: 20px"
/>
<el-form
ref="formRef"
:hide-required-asterisk="true"
:model="form"
:rules="rules"
style="margin-top: 20px"
>
<com-detail title="与表单结合并自定义插槽" :data="form" :schema="fromSchema">
<template #title="scope">
<span class="is-required-item">{{ scope.row.label }}</span>
</template>
<template #titleContent>
<el-form-item prop="title">
<el-input v-model="form.title" placeholder="请输入标题" />
</el-form-item>
</template>
<template #author="scope">
<span class="is-required-item">{{ scope.row.label }}</span>
</template>
<template #authorContent>
<el-form-item prop="author">
<el-input v-model="form.author" placeholder="请输入作者" />
</el-form-item>
</template>
<template #display_time="scope">
<span class="is-required-item">{{ scope.row.label }}</span>
</template>
<template #display_timeContent>
<el-form-item prop="display_time">
<el-date-picker
v-model="form.display_time"
type="datetime"
placeholder="请选择创建时间"
style="width: 100%"
/>
</el-form-item>
</template>
<template #importance="scope">
<span class="is-required-item">{{ scope.row.label }}</span>
</template>
<template #importanceContent>
<el-form-item prop="importance">
<el-select v-model="form.importance" placeholder="请选择重要性" style="width: 100%">
<el-option label="重要" value="3" />
<el-option label="良好" value="2" />
<el-option label="一般" value="1" />
</el-select>
</el-form-item>
</template>
<template #pageviews="scope">
<span class="is-required-item">{{ scope.row.label }}</span>
</template>
<template #pageviewsContent>
<el-form-item prop="pageviews">
<el-input-number
v-model="form.pageviews"
:min="0"
:max="99999999"
style="width: 100%"
/>
</el-form-item>
</template>
</com-detail>
<div style="margin-top: 15px; text-align: center">
<el-button type="primary" @click="saveData">保存(控制台查看数据)</el-button>
</div>
</el-form>
</div>
</template>
<script setup lang="ts" name="DetailDemo">
import { reactive, ref, unref } from 'vue'
import { SchemaConfig } from '_c/ComDetail/types'
const formRef = ref<Nullable<any>>(null)
const requiredRule: {
required: boolean
message: string
} = {
required: true,
message: '该项为必填项'
}
const data = reactive<IObj>({
username: 'chenkl',
nickName: '梦似花落。',
age: 26,
phone: '13655971xxxx',
email: '502431556@qq.com',
addr: '这是一个很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长的地址',
sex: '男',
certy: '35058319940712xxxx'
})
const schema = reactive<SchemaConfig[]>([
{
field: 'username',
label: '用户名'
},
{
field: 'nickName',
label: '昵称'
},
{
field: 'phone',
label: '联系电话'
},
{
field: 'email',
label: '邮箱'
},
{
field: 'addr',
label: '地址',
span: 24
}
])
const fromSchema = reactive<SchemaConfig[]>([
{
field: 'title',
label: '标题',
span: 24
},
{
field: 'author',
label: '作者'
},
{
field: 'display_time',
label: '创建时间'
},
{
field: 'importance',
label: '重要性'
},
{
field: 'pageviews',
label: '阅读数'
}
])
const form = reactive<IObj>({
id: '', // id
author: '', // 作者
title: '', // 标题
importance: '', // 重要性
display_time: '', // 创建时间
pageviews: 0 // 阅读数
})
const rules = reactive<IObj>({
title: [requiredRule],
author: [requiredRule],
importance: [requiredRule],
display_time: [requiredRule],
pageviews: [requiredRule]
})
function saveData() {
try {
;(unref(formRef) as any).validate((valid) => {
if (valid) {
console.log(form)
} else {
console.log('error submit!!')
return false
}
})
} catch (err) {
console.log(err)
}
}
</script>

View File

@@ -1,27 +0,0 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="对 Element 的 Dialog 组件进行二次封装,支持所有原生参数。"
type="info"
style="margin-bottom: 20px"
/>
<el-button type="primary" @click="visible = true">打开弹窗</el-button>
<com-dialog v-model="visible" title="提示">
<div style="height: 1000px"> 我是弹窗内容 </div>
<template #footer>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="visible = false">确定</el-button>
</template>
</com-dialog>
</div>
</template>
<script setup lang="ts" name="DialogDemo">
import { ref } from 'vue'
const visible = ref<boolean>(false)
</script>
<style></style>

View File

@@ -1,306 +0,0 @@
import { EChartsOption } from 'echarts'
import { EChartsOption as EChartsWordOption } from 'echarts-wordcloud'
export const lineOptions: EChartsOption = {
xAxis: {
data: [
'一月',
'二月',
'三月',
'四月',
'五月',
'六月',
'七月',
'八月',
'九月',
'十月',
'十一月',
'十二月'
],
boundaryGap: false,
axisTick: {
show: false
}
},
grid: {
left: 20,
right: 20,
bottom: 20,
top: 30,
containLabel: true
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
},
padding: [5, 10]
},
yAxis: {
axisTick: {
show: false
}
},
legend: {
data: ['预期', '实际']
},
series: [
{
name: '预期',
smooth: true,
type: 'line',
data: [100, 120, 161, 134, 105, 160, 165, 114, 163, 185, 118, 123],
animationDuration: 2800,
animationEasing: 'cubicInOut'
},
{
name: '实际',
smooth: true,
type: 'line',
itemStyle: {},
data: [120, 82, 91, 154, 162, 140, 145, 250, 134, 56, 99, 123],
animationDuration: 2800,
animationEasing: 'quadraticOut'
}
]
}
export const pieOptions: EChartsOption = {
title: {
text: '用户访问来源',
left: 'center'
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left',
data: ['直接访问', '邮件营销', '联盟广告', '视频广告', '搜索引擎']
},
series: [
{
name: '用户访问来源',
type: 'pie',
radius: '55%',
center: ['50%', '60%'],
data: [
{ value: 335, name: '直接访问' },
{ value: 310, name: '邮件营销' },
{ value: 234, name: '联盟广告' },
{ value: 135, name: '视频广告' },
{ value: 1548, name: '搜索引擎' }
]
}
]
}
export const barOptions: EChartsOption = {
title: {
text: '每周用户活跃量',
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
xAxis: {
type: 'category',
data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
axisTick: {
alignWithLabel: true
}
},
yAxis: {
type: 'value'
},
series: [
{
name: '活跃量',
data: [13253, 34235, 26321, 12340, 24643, 1322, 1324],
type: 'bar'
}
]
}
export const pieOptions2: EChartsOption = {
title: {
text: '用户访问来源',
left: 'center'
},
tooltip: {
trigger: 'item',
formatter: '{a} <br/>{b} : {c} ({d}%)'
},
legend: {
orient: 'vertical',
left: 'left'
},
series: [
{
name: '访问来源',
type: 'pie',
radius: '55%',
center: ['50%', '50%'],
data: [
{
value: 335,
name: '直接访问'
},
{
value: 310,
name: '邮件营销'
},
{
value: 274,
name: '联盟广告'
},
{
value: 235,
name: '视频广告'
},
{
value: 400,
name: '搜索引擎'
}
].sort(function (a, b) {
return a.value - b.value
}),
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
}
export const wordOptions: EChartsWordOption = {
tooltip: {},
series: [
{
type: 'wordCloud',
gridSize: 2,
sizeRange: [12, 50],
rotationRange: [-90, 90],
shape: 'pentagon',
width: 600,
height: 400,
drawOutOfBound: true,
textStyle: {
color: function () {
return (
'rgb(' +
[
Math.round(Math.random() * 160),
Math.round(Math.random() * 160),
Math.round(Math.random() * 160)
].join(',') +
')'
)
}
},
emphasis: {
textStyle: {
shadowBlur: 10,
shadowColor: '#333'
}
},
data: [
{
name: 'Sam S Club',
value: 10000,
textStyle: {
color: 'black'
},
emphasis: {
textStyle: {
color: 'red'
}
}
},
{
name: 'Macys',
value: 6181
},
{
name: 'Amy Schumer',
value: 4386
},
{
name: 'Jurassic World',
value: 4055
},
{
name: 'Charter Communications',
value: 2467
},
{
name: 'Chick Fil A',
value: 2244
},
{
name: 'Planet Fitness',
value: 1898
},
{
name: 'Pitch Perfect',
value: 1484
},
{
name: 'Express',
value: 1112
},
{
name: 'Home',
value: 965
},
{
name: 'Johnny Depp',
value: 847
},
{
name: 'Lena Dunham',
value: 582
},
{
name: 'Lewis Hamilton',
value: 555
},
{
name: 'KXAN',
value: 550
},
{
name: 'Mary Ellen Mark',
value: 462
},
{
name: 'Farrah Abraham',
value: 366
},
{
name: 'Rita Ora',
value: 360
},
{
name: 'Serena Williams',
value: 282
},
{
name: 'NCAA baseball tournament',
value: 273
},
{
name: 'Point Break',
value: 265
}
]
}
]
}

View File

@@ -1,53 +0,0 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="统一封装 Echart 组件,自适应宽度,只需传入 options 与 height 属性即可展示对应的图表。"
type="info"
style="margin-bottom: 20px"
/>
<el-row :gutter="20">
<el-col :span="10">
<div class="chart-wrap">
<echart :height="'300px'" :options="pieOptions" />
</div>
</el-col>
<el-col :span="14">
<div class="chart-wrap">
<echart :options="barOptions" :height="'300px'" />
</div>
</el-col>
<el-col :span="14">
<div class="chart-wrap">
<echart :options="lineOptions" :height="'300px'" />
</div>
</el-col>
<el-col :span="10">
<div class="chart-wrap">
<echart :options="pieOptions2" :height="'300px'" />
</div>
</el-col>
<el-col :span="24">
<div class="chart-wrap">
<echart :options="wordOptions" :height="'300px'" />
</div>
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts" name="EchartsDemo">
import { lineOptions, pieOptions, barOptions, pieOptions2, wordOptions } from './echart-data'
import Echart from '_c/Echart/index.vue'
</script>
<style lang="less" scoped>
.chart-wrap {
padding: 10px;
margin-bottom: 20px;
background-color: #fff;
border-radius: 5px;
}
</style>

View File

@@ -1,45 +0,0 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="基于 wangeditor 封装的 富文本 组件。"
type="info"
style="margin-bottom: 20px"
/>
<editor ref="editorRef" :value="content" @change="handleChange" />
<div style="margin-top: 20px; text-align: center">
<el-button @click="showHtml"> 获取TTML(请在控制台查看) </el-button>
<el-button @click="showText"> 获取TEXT(请在控制台查看) </el-button>
<el-button @click="showJson"> 获取JSON(请在控制台查看) </el-button>
</div>
</div>
</template>
<script setup lang="ts" name="EditorDemo">
import { ref, unref } from 'vue'
import Editor from '_c/Editor/index.vue'
const content = ref<string>('默认展示数据')
const editorRef = ref<Nullable<any>>(null)
function handleChange(html: string) {
console.log(html)
}
function showHtml() {
console.log((unref(editorRef) as any).getHtml())
}
function showText() {
console.log((unref(editorRef) as any).getText())
}
function showJson() {
console.log((unref(editorRef) as any).getJSON())
}
</script>
<style></style>

View File

@@ -1,38 +0,0 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="文字高亮组件 -- 基础用法"
type="info"
style="margin-bottom: 20px"
/>
<highlight :keys="['vue-element-plus-admin', 'vue-element-admin', 'vben-admin']">
vue-element-plus-admin是一个基于vue3vite2element-plus的后台解决方案借鉴了vue-element-admin和vben-admin的写法和优点内置了动态路由权限验证典型的业务模型丰富的功能组件并且提供了多页配置开箱即用可以用来作为项目的启动模版它可以帮助你快速搭建企业级中后台产品原型也可以作为一个示例用于学习
</highlight>
<el-alert
effect="dark"
:closable="false"
title="文字高亮组件 -- 点击事件"
type="info"
style="margin-top: 20px; margin-bottom: 20px"
/>
<highlight
:keys="['vue-element-plus-admin', 'vue-element-admin', 'vben-admin']"
@click="keyClick"
>
vue-element-plus-admin是一个基于vue3vite2element-plus的后台解决方案借鉴了vue-element-admin和vben-admin的写法和优点内置了动态路由权限验证典型的业务模型丰富的功能组件并且提供了多页配置开箱即用可以用来作为项目的启动模版它可以帮助你快速搭建企业级中后台产品原型也可以作为一个示例用于学习
</highlight>
</div>
</template>
<script setup lang="ts" name="HighlightDemo">
import Highlight from '_c/Highlight/index'
import { Message } from '_c/Message'
function keyClick(key: string) {
Message.success(key)
}
</script>
<style></style>

View File

@@ -1,24 +0,0 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="二次封装 Element 的 Message 组件,每次只显示最新一条消息,避免出现太多消息提示导致不美观。"
type="info"
style="margin-bottom: 20px"
/>
<el-button @click="show">显示</el-button>
</div>
</template>
<script setup lang="ts" name="MessageDemo">
import { ref } from 'vue'
import { Message } from '_c/Message'
const count = ref<number>(0)
function show() {
count.value = count.value + 1
Message.success('这是成功消息' + count.value)
}
</script>
<style></style>

View File

@@ -1,113 +0,0 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="抽取于 Element 的图片预览组件进行改造,实现函数式调用组件,无需基于图片进行点击预览。"
type="info"
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"
:key="item"
class="img-item"
@click="showHasImg($index)"
>
<img :src="item" alt="" />
</div>
</div>
<el-alert
effect="dark"
:closable="false"
title="无底图预览。"
type="info"
style="margin-top: 20px; margin-bottom: 20px"
/>
<el-button type="primary" @click="showNoImg">点击预览</el-button>
<el-alert
effect="dark"
:closable="false"
title="点击事件,包含图片点击事件以及关闭事件。"
type="info"
style="margin-top: 20px; margin-bottom: 20px"
/>
<el-button type="primary" @click="showImg">点击预览</el-button>
</div>
</template>
<script setup lang="ts" name="PreviewDemo">
import { ref } from 'vue'
import { createImgPreview } from '_c/Preview'
import { Message } from '_c/Message'
const imgList = ref<string[]>([
'https://img1.baidu.com/it/u=657828739,1486746195&fm=26&fmt=auto&gp=0.jpg',
'https://img0.baidu.com/it/u=3114228356,677481409&fm=26&fmt=auto&gp=0.jpg',
'https://img1.baidu.com/it/u=508846955,3814747122&fm=26&fmt=auto&gp=0.jpg',
'https://img1.baidu.com/it/u=3536647690,3616605490&fm=26&fmt=auto&gp=0.jpg',
'https://img1.baidu.com/it/u=4087287201,1148061266&fm=26&fmt=auto&gp=0.jpg',
'https://img2.baidu.com/it/u=3429163260,2974496379&fm=26&fmt=auto&gp=0.jpg'
])
function showHasImg(i: number) {
createImgPreview({
index: i,
imageList: imgList.value
})
}
function showNoImg() {
createImgPreview({
index: 0,
imageList: imgList.value
})
}
function showImg() {
createImgPreview({
index: 0,
imageList: imgList.value,
onClose: (i: number) => {
Message.info('关闭的图片索引:' + i)
},
onSelect: (i: number) => {
Message.info('当前点击的图片索引:' + i)
}
})
}
</script>
<style lang="less" scoped>
.img-wrap {
display: flex;
justify-content: center;
.img-item {
position: relative;
width: 400px;
height: 300px;
margin: 0 10px;
overflow: hidden;
cursor: pointer;
img {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
}
</style>

View File

@@ -1,90 +0,0 @@
<template>
<div>
<el-row :gutter="20">
<el-col :span="6">
<div class="title-item">基础用法默认canvas</div>
<qrcode text="vue-element-plus-admin" />
</el-col>
<el-col :span="6">
<div class="title-item">img标签</div>
<qrcode text="vue-element-plus-admin" tag="img" />
</el-col>
<el-col :span="6">
<div class="title-item">样式配置</div>
<qrcode
text="vue-element-plus-admin"
:options="{
color: {
dark: '#55D187',
light: '#2d8cf0'
}
}"
/>
</el-col>
<el-col :span="6">
<div class="title-item">点击</div>
<qrcode text="vue-element-plus-admin" @click="codeClick" />
</el-col>
<el-col :span="6">
<div class="title-item">异步内容</div>
<qrcode :text="text" />
</el-col>
<el-col :span="6">
<div class="title-item">二维码失效</div>
<qrcode text="vue-element-plus-admin" :disabled="true" @disabled-click="disabledClick" />
</el-col>
<el-col :span="6">
<div class="title-item">logo配置</div>
<qrcode text="vue-element-plus-admin" :logo="logoImg" />
</el-col>
<el-col :span="6">
<div class="title-item">logo样式配置</div>
<qrcode
text="vue-element-plus-admin"
:logo="{
src: logoImg,
logoSize: 0.2,
borderSize: 0.05,
borderRadius: 50,
bgColor: 'blue'
}"
/>
</el-col>
<el-col :span="6">
<div class="title-item">大小配置</div>
<qrcode text="vue-element-plus-admin" :width="300" />
</el-col>
</el-row>
</div>
</template>
<script setup lang="ts" name="QrcodeDemo">
import { ref } from 'vue'
import Qrcode from '_c/Qrcode/index.vue'
import { Message } from '_c/Message'
import logoImg from '@/assets/img/logo.png'
const text = ref<string>('')
setTimeout(() => {
text.value = '我是异步生成的内容'
}, 3000)
function codeClick() {
Message.info('我被点击了。')
}
function disabledClick() {
Message.info('我失效被点击了。')
}
</script>
<style lang="less" scoped>
.el-col {
margin-bottom: 20px;
text-align: center;
.title-item {
margin-bottom: 10px;
font-weight: bold;
}
}
</style>

View File

@@ -1,149 +0,0 @@
export const classicData = [
{
label: '即时配送',
value: true,
itemType: 'switch',
field: 'delivery'
},
{
label: '活动名称',
value: '',
itemType: 'input',
field: 'name',
placeholder: '活动名称',
clearable: true,
rules: [
{
required: true,
message: '请输入活动名称'
}
]
},
{
label: '活动区域',
value: '',
itemType: 'select',
placeholder: '活动区域',
clearable: true,
field: 'region',
options: [
{
title: '区域一',
value: 'fujian'
},
{
title: '区域二',
value: 'beijing'
}
],
rules: [
{
itemType: 'string',
required: true,
message: '请选择活动区域'
}
]
},
{
label: '特殊资源',
value: '2',
itemType: 'radio',
field: 'resource',
radioType: 'button', // button or radio
options: [
{
label: '线上品牌商赞助',
value: '1'
},
{
label: '线下场地免费',
value: '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: '',
itemType: 'datePicker',
field: 'date1',
clearable: true,
format: 'YYYY-MM-DD',
placeholder: '请选择日期'
},
{
label: '月选择器',
value: '',
itemType: 'datePicker',
field: 'date2',
clearable: true,
format: 'YYYY-MM',
placeholder: '请选择日期'
},
{
label: '范围选择器',
value: [],
itemType: 'datePicker',
field: 'date3',
clearable: true,
type: 'daterange',
rangeSeparator: '至',
startPlaceholder: '开始日期',
endPlaceholder: '结束日期'
},
{
label: '周选择器',
value: '',
itemType: 'datePicker',
field: 'date4',
type: 'week',
clearable: true,
placeholder: '请选择日期'
}
]

View File

@@ -1,95 +0,0 @@
<template>
<div>
<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-top: 20px; margin-bottom: 20px"
/>
<div class="searh">
<com-search :data="classicData" @search-submit="searchSubmit1" @reset-submit="resetSubmit1" />
<div> 查询/重置后的数据{{ formData1 }} </div>
</div>
<el-alert
effect="dark"
:closable="false"
title="底部操作按钮风格。"
type="info"
style="margin-top: 20px; margin-bottom: 20px"
/>
<div class="searh">
<com-search
layout="bottom"
:data="classicData"
@search-submit="searchSubmit2"
@reset-submit="resetSubmit2"
/>
<div> 查询/重置后的数据{{ formData2 }} </div>
</div>
<el-alert
effect="dark"
:closable="false"
title="右侧操作按钮风格。"
type="info"
style="margin-top: 20px; margin-bottom: 20px"
/>
<div class="searh">
<com-search
layout="right"
:data="classicData"
@search-submit="searchSubmit3"
@reset-submit="resetSubmit3"
/>
<div> 查询/重置后的数据{{ formData3 }} </div>
</div>
</div>
</template>
<script setup lang="ts" name="SearchDemo">
import { ref } from 'vue'
import { classicData } from './data'
const formData1 = ref<Nullable<IObj>>(null)
const formData2 = ref<Nullable<IObj>>(null)
const formData3 = ref<Nullable<IObj>>(null)
function searchSubmit1(data: any): void {
formData1.value = data
}
function resetSubmit1(data: any): void {
formData1.value = data
}
function searchSubmit2(data: any): void {
formData2.value = data
}
function resetSubmit2(data: any): void {
formData2.value = data
}
function searchSubmit3(data: any): void {
formData3.value = data
}
function resetSubmit3(data: any): void {
formData3.value = data
}
</script>
<style lang="less" scoped>
.searh {
padding: 20px;
background: #fff;
}
</style>

View File

@@ -1,26 +0,0 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="useWatermark为整个系统提供水印功能。"
type="info"
style="margin-bottom: 20px"
/>
<el-button type="primary" @click="setWatermark('vue-element-plus-admin')">创建水印</el-button>
<el-button type="danger" @click="clear">清除水印</el-button>
<el-button type="warning" @click="setWatermark('vue-element-plus-admin-new')">
重置水印
</el-button>
</div>
</template>
<script setup lang="ts" name="Watermark">
import { onBeforeUnmount } from 'vue'
import { useWatermark } from '@/hooks/web/useWatermark'
const { setWatermark, clear } = useWatermark()
onBeforeUnmount(() => {
clear()
})
</script>

View File

@@ -1,147 +0,0 @@
<template>
<el-row :gutter="20" class="panel-group">
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
<div class="card-panel" @click="handleSetLineChartData('newVisitis')">
<div class="card-panel-icon-wrapper icon-people">
<svg-icon icon-class="peoples" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">新增用户</div>
<count-to :start-val="0" :end-val="102400" :duration="2600" class="card-panel-num" />
</div>
</div>
</el-col>
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
<div class="card-panel" @click="handleSetLineChartData('messages')">
<div class="card-panel-icon-wrapper icon-message">
<svg-icon icon-class="message" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">未读信息</div>
<count-to :start-val="0" :end-val="81212" :duration="3000" class="card-panel-num" />
</div>
</div>
</el-col>
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
<div class="card-panel" @click="handleSetLineChartData('purchases')">
<div class="card-panel-icon-wrapper icon-money">
<svg-icon icon-class="money" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">成交金额</div>
<count-to :start-val="0" :end-val="9280" :duration="3200" class="card-panel-num" />
</div>
</div>
</el-col>
<el-col :xs="12" :sm="12" :lg="6" class="card-panel-col">
<div class="card-panel" @click="handleSetLineChartData('shoppings')">
<div class="card-panel-icon-wrapper icon-shopping">
<svg-icon icon-class="shopping" class-name="card-panel-icon" />
</div>
<div class="card-panel-description">
<div class="card-panel-text">购物总量</div>
<count-to :start-val="0" :end-val="13600" :duration="3600" class="card-panel-num" />
</div>
</div>
</el-col>
</el-row>
</template>
<script setup lang="ts" name="PanelGroup">
import CountTo from '_c/CountTo/index.vue'
const emit = defineEmits(['handleSetLineChartData'])
function handleSetLineChartData(type: string) {
emit('handleSetLineChartData', type)
}
</script>
<style lang="less" scoped>
.panel-group {
.card-panel-col {
margin-bottom: 20px;
}
.card-panel {
position: relative;
height: 108px;
overflow: hidden;
font-size: 12px;
color: #666;
cursor: pointer;
background: #fff;
border-color: rgba(0, 0, 0, 0.05);
box-shadow: 4px 4px 40px rgba(0, 0, 0, 0.05);
&:hover {
.card-panel-icon-wrapper {
color: #fff;
}
.icon-people {
background: #40c9c6;
}
.icon-message {
background: #36a3f7;
}
.icon-money {
background: #f4516c;
}
.icon-shopping {
background: #34bfa3;
}
}
.icon-people {
color: #40c9c6;
}
.icon-message {
color: #36a3f7;
}
.icon-money {
color: #f4516c;
}
.icon-shopping {
color: #34bfa3;
}
.card-panel-icon-wrapper {
float: left;
padding: 16px;
margin: 14px 0 0 14px;
border-radius: 6px;
transition: all 0.38s ease-out;
}
.card-panel-icon {
float: left;
font-size: 48px;
}
.card-panel-description {
float: right;
margin: 26px;
margin-left: 0;
font-weight: bold;
.card-panel-text {
margin-bottom: 12px;
font-size: 16px;
line-height: 18px;
color: rgba(0, 0, 0, 0.45);
}
.card-panel-num {
font-size: 20px;
}
}
}
}
</style>

View File

@@ -1,6 +1,5 @@
<template>
<div>
<panel-group />
<el-row :gutter="20">
<el-col :span="10">
<div class="chart__wrap">
@@ -22,7 +21,6 @@
<script setup lang="ts" name="Dashboard">
import { lineOptions, pieOptions, barOptions } from './echart-data'
import Echart from '_c/Echart/index.vue'
import PanelGroup from './components/PanelGroup.vue'
</script>
<style lang="less" scoped>

View File

@@ -1,61 +0,0 @@
<template>
<div>
<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-top: 20px; margin-bottom: 20px"
/>
<div class="input__wrap">
<el-input v-model="inputVal1" placeholder="请输入要复制的文本" />
<el-button v-clipboard="inputVal1" type="primary">复制</el-button>
</div>
<el-alert
effect="dark"
:closable="false"
title="自定义回调方法。"
type="info"
style="margin-top: 20px; margin-bottom: 20px"
/>
<div class="input__wrap">
<el-input v-model="inputVal2" placeholder="请输入要复制的文本" />
<el-button
v-clipboard="inputVal2"
v-clipboard:success="clipboardSuccess"
v-clipboard:error="clipboardError"
type="primary"
>复制</el-button
>
</div>
</div>
</template>
<script setup lang="ts" name="DirectivesDemo">
import { ref } from 'vue'
import { Message } from '_c/Message'
const inputVal1 = ref<string>('')
const inputVal2 = ref<string>('')
function clipboardSuccess(val: any) {
Message.success('我是自定义成功回调:' + val.text)
}
function clipboardError() {
Message.error('我是自定义失败回调')
}
</script>
<style lang="less" scoped>
.input__wrap {
display: flex;
padding: 20px;
background: #fff;
}
</style>

View File

@@ -1,17 +0,0 @@
import fetch from '@/axios-config'
export const getExampleListApi = ({ params }: any) => {
return fetch({ url: '/example/list', method: 'get', params })
}
export const delsExampApi = ({ data }: any) => {
return fetch({ url: '/example/delete', method: 'post', data })
}
export const setExampApi = ({ data }: any) => {
return fetch({ url: '/example/save', method: 'post', data })
}
export const getExampDetApi = ({ params }: any) => {
return fetch({ url: '/example/detail', method: 'get', params })
}

View File

@@ -1,96 +0,0 @@
<template>
<div>
<com-detail :data="form" :schema="fromSchema" :collapsed="false" title="文章详情">
<template #contentContent="scope">
<div v-html="scope.row.content"></div>
</template>
</com-detail>
<div class="dialong__button--wrap">
<el-button @click="close">取消</el-button>
</div>
</div>
</template>
<script setup lang="ts" name="Detail">
import { PropType, reactive } from 'vue'
import { getExampDetApi } from '../api'
import { SchemaConfig } from '_c/ComDetail/types'
const fromSchema: SchemaConfig[] = [
{
field: 'title',
label: '标题',
span: 24
},
{
field: 'author',
label: '作者'
},
{
field: 'display_time',
label: '创建时间'
},
{
field: 'importance',
label: '重要性'
},
{
field: 'pageviews',
label: '阅读数'
},
{
field: 'content',
label: '内容',
span: 24
}
]
const props = defineProps({
info: {
type: Object as PropType<Nullable<IObj>>,
default: () => null
}
})
const emit = defineEmits(['close'])
const form = reactive<IObj>({
id: '', // id
author: '', // 作者
title: '', // 标题
content: '', // 内容
importance: '', // 重要性
display_time: '', // 创建时间
pageviews: 0 // 阅读数
})
async function getDet() {
if (props.info) {
const id = props.info.id
try {
const res: any = await getExampDetApi({
params: {
id: id
}
})
if (res) {
for (const key in form) {
if (key === 'importance') {
form[key] = (res.data[key] as number).toString()
} else {
form[key] = res.data[key]
}
}
}
} catch (e) {
console.log(e)
}
}
}
function close() {
emit('close')
}
getDet()
</script>

View File

@@ -1,158 +0,0 @@
<template>
<div>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-row>
<el-col :span="24">
<el-form-item prop="title" label="标题">
<el-input v-model="form.title" placeholder="请输入标题" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="author" label="作者">
<el-input v-model="form.author" placeholder="请输入作者" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="display_time" label="创建时间">
<el-date-picker
v-model="form.display_time"
type="datetime"
placeholder="请选择创建时间"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="importance" label="重要性">
<el-select v-model="form.importance" placeholder="请选择重要性" style="width: 100%">
<el-option label="重要" value="3" />
<el-option label="良好" value="2" />
<el-option label="一般" value="1" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="pageviews" label="阅读数">
<el-input-number
v-model="form.pageviews"
:min="0"
:max="99999999"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item prop="content" label="内容">
<editor ref="editorRef" :value="form.content" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="dialong__button--wrap">
<el-button @click="close">取消</el-button>
<el-button :loading="subLoading" type="primary" @click="setListData">保存</el-button>
</div>
</div>
</template>
<script setup lang="ts" name="IfnoWrite">
import { PropType, reactive, ref } from 'vue'
import { setExampApi, getExampDetApi } from '../api'
import Editor from '_c/Editor/index.vue'
import { Message } from '_c/Message'
const requiredRule: {
required: boolean
message: string
} = {
required: true,
message: '该项为必填项'
}
const props = defineProps({
info: {
type: Object as PropType<Nullable<IObj>>,
default: () => null
}
})
const emit = defineEmits(['success', 'close'])
const editorRef = ref<Nullable<HTMLElement>>(null)
const formRef = ref<Nullable<HTMLElement>>(null)
const subLoading = ref<boolean>(false)
const form = reactive<IObj>({
id: '', // id
author: '', // 作者
title: '', // 标题
content: '', // 内容
importance: '', // 重要性
display_time: '', // 创建时间
pageviews: 0 // 阅读数
})
const rules = reactive<IObj>({
title: [requiredRule],
author: [requiredRule],
content: [requiredRule],
importance: [requiredRule],
display_time: [requiredRule],
pageviews: [requiredRule]
})
async function getDet() {
if (props.info) {
const id = props.info.id
try {
const res: any = await getExampDetApi({
params: {
id: id
}
})
if (res) {
for (const key in form) {
if (key === 'importance') {
form[key] = (res.data[key] as number).toString()
} else {
form[key] = res.data[key]
}
}
}
} catch (e) {
console.log(e)
}
}
}
// 新增或者编辑
function setListData() {
try {
subLoading.value = true
form.content = (editorRef.value as any).getHtml()
;(formRef.value as any).validate(async (valid) => {
if (valid) {
const res = await setExampApi({
data: form
})
if (res) {
Message.success(form.id ? '编辑成功' : '新增成功')
emit('success', form.id ? 'edit' : 'add')
}
} else {
console.log('error submit!!')
return false
}
})
} catch (err) {
console.log(err)
} finally {
subLoading.value = false
}
}
function close() {
emit('close')
}
getDet()
</script>

View File

@@ -1,18 +0,0 @@
export interface InfoWriteParams {
title: string
id?: string
author: string
content: string
importance: string
display_time: string
pageviews: number
}
export interface InfoWriteRules {
title?: any[]
author?: any[]
content?: any[]
importance?: any[]
display_time?: any[]
pageviews?: any[]
}

View File

@@ -1,135 +0,0 @@
<template>
<div>
<div class="search__example--wrap">
<com-search :data="searchData" @search-submit="searchSubmit" @reset-submit="resetSubmit" />
</div>
<div class="button__example--wrap">
<el-button type="primary" icon="el-icon-circle-plus-outline" @click="open(null, 'InfoWrite')">
新增
</el-button>
<el-button type="danger" icon="el-icon-delete" @click="dels">删除</el-button>
</div>
<com-table
v-loading="loading"
selection
:columns="columns"
:data="tableData"
:pagination="{
currentPage: defaultParams.pageIndex,
total: total,
onSizeChange: handleSizeChange,
onCurrentChange: handleCurrentChange
}"
@selection-change="handleSelectionChange"
>
<template #importance="scope">
<el-tag
:type="
scope.row.importance === 3
? 'success'
: scope.row.importance === 2
? 'warning'
: 'danger'
"
>
{{ scope.row.importance === 3 ? '重要' : scope.row.importance === 2 ? '良好' : '一般' }}
</el-tag>
</template>
<template #action="scope">
<el-button type="primary" size="mini" @click="open(scope.row, 'InfoWrite')">编辑</el-button>
<el-button type="success" size="mini" @click="open(scope.row, 'Detail')">查看</el-button>
<el-button type="danger" size="mini" @click="dels(scope.row)">删除</el-button>
</template>
</com-table>
<com-dialog v-model="dialogVisible" :title="dialogTitle">
<info-write
v-if="comName === 'InfoWrite' && dialogVisible"
:info="rowData"
@close="toggleVisible"
@success="refreshTable"
/>
<detail v-if="comName === 'Detail' && dialogVisible" :info="rowData" @close="toggleVisible" />
</com-dialog>
</div>
</template>
<script setup lang="ts" name="ExampleDialog">
import { getExampleListApi, delsExampApi } from './api'
import { useWork } from '@/hooks/work/useWork'
import InfoWrite from './components/InfoWrite.vue'
import Detail from './components/Detail.vue'
const {
defaultParams,
tableData,
loading,
total,
dialogVisible,
dialogTitle,
comName,
rowData,
handleSizeChange,
handleCurrentChange,
handleSelectionChange,
toggleVisible,
getList,
searchSubmit,
resetSubmit,
open,
refreshTable,
dels
} = useWork({
listFun: getExampleListApi,
delFun: delsExampApi
})
const searchData = [
{
label: '标题',
value: '',
itemType: 'input',
field: 'title',
placeholder: '请输入标题',
clearable: true
}
]
const columns = [
{
field: 'title',
label: '标题',
showOverflowTooltip: true
},
{
field: 'author',
label: '作者'
},
{
field: 'display_time',
label: '创建时间'
},
{
field: 'importance',
label: '重要性',
slots: {
default: 'importance'
}
},
{
field: 'pageviews',
label: '阅读数'
},
{
field: 'action',
label: '操作',
width: '220px',
slots: {
default: 'action'
}
}
]
getList()
</script>

View File

@@ -1,17 +0,0 @@
import fetch from '@/axios-config'
export const getExampleListApi = ({ params }: any) => {
return fetch({ url: '/example/list2', method: 'get', params })
}
export const delsExampApi = ({ data }: any) => {
return fetch({ url: '/example/delete', method: 'post', data })
}
export const setExampApi = ({ data }: any) => {
return fetch({ url: '/example/save', method: 'post', data })
}
export const getExampDetApi = ({ params }: any) => {
return fetch({ url: '/example/detail', method: 'get', params })
}

View File

@@ -1,98 +0,0 @@
<template>
<div>
<com-detail :data="form" :schema="fromSchema" :collapsed="false" title="文章详情">
<template #contentContent="scope">
<div v-html="scope.row.content"></div>
</template>
</com-detail>
<div class="dialong__button--wrap">
<el-button @click="close">取消</el-button>
</div>
</div>
</template>
<script setup lang="ts" name="Detail">
import { PropType, reactive } from 'vue'
import { getExampDetApi } from '../api'
import { useRouter } from 'vue-router'
const { push } = useRouter()
const props = defineProps({
id: {
type: String as PropType<string>,
default: ''
}
})
const fromSchema = [
{
field: 'title',
label: '标题',
span: 24
},
{
field: 'author',
label: '作者'
},
{
field: 'display_time',
label: '创建时间'
},
{
field: 'importance',
label: '重要性'
},
{
field: 'pageviews',
label: '阅读数'
},
{
field: 'content',
label: '内容',
span: 24,
slots: {
default: 'content'
}
}
]
const form = reactive<IObj>({
id: '', // id
author: '', // 作者
title: '', // 标题
content: '', // 内容
importance: '', // 重要性
display_time: '', // 创建时间
pageviews: 0 // 阅读数
})
async function getDet() {
if (props.id) {
const id = props.id
try {
const res: any = await getExampDetApi({
params: {
id: id
}
})
if (res) {
for (const key in form) {
if (key === 'importance') {
form[key] = (res.data[key] as number).toString()
} else {
form[key] = res.data[key]
}
}
}
} catch (e) {
console.log(e)
}
}
}
function close() {
push('/example-demo/example-page')
}
getDet()
</script>

View File

@@ -1,160 +0,0 @@
<template>
<div>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-row>
<el-col :span="24">
<el-form-item prop="title" label="标题">
<el-input v-model="form.title" placeholder="请输入标题" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="author" label="作者">
<el-input v-model="form.author" placeholder="请输入作者" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="display_time" label="创建时间">
<el-date-picker
v-model="form.display_time"
type="datetime"
placeholder="请选择创建时间"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="importance" label="重要性">
<el-select v-model="form.importance" placeholder="请选择重要性" style="width: 100%">
<el-option label="重要" value="3" />
<el-option label="良好" value="2" />
<el-option label="一般" value="1" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="pageviews" label="阅读数">
<el-input-number
v-model="form.pageviews"
:min="0"
:max="99999999"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item prop="content" label="内容">
<editor ref="editorRef" :value="form.content" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="dialong__button--wrap">
<el-button @click="close">取消</el-button>
<el-button :loading="subLoading" type="primary" @click="setListData">保存</el-button>
</div>
</div>
</template>
<script setup lang="ts" name="InfoWrite">
import { PropType, ref, reactive } from 'vue'
import { setExampApi, getExampDetApi } from '../api'
import Editor from '_c/Editor/index.vue'
import { Message } from '_c/Message'
import { useRouter } from 'vue-router'
const { push } = useRouter()
const requiredRule: {
required: boolean
message: string
} = {
required: true,
message: '该项为必填项'
}
const props = defineProps({
id: {
type: String as PropType<string>,
default: ''
}
})
const emit = defineEmits(['success'])
const editorRef = ref<Nullable<HTMLElement>>(null)
const formRef = ref<Nullable<HTMLElement>>(null)
const subLoading = ref<boolean>(false)
const form = reactive<IObj>({
id: '', // id
author: '', // 作者
title: '', // 标题
content: '', // 内容
importance: '', // 重要性
display_time: '', // 创建时间
pageviews: 0 // 阅读数
})
const rules = reactive<IObj>({
title: [requiredRule],
author: [requiredRule],
content: [requiredRule],
importance: [requiredRule],
display_time: [requiredRule],
pageviews: [requiredRule]
})
async function getDet() {
if (props.id) {
const id = props.id
try {
const res: any = await getExampDetApi({
params: {
id: id
}
})
if (res) {
for (const key in form) {
if (key === 'importance') {
form[key] = (res.data[key] as number).toString()
} else {
form[key] = res.data[key]
}
}
}
} catch (e) {
console.log(e)
}
}
}
// 新增或者编辑
function setListData() {
try {
subLoading.value = true
form.content = (editorRef.value as any).getHtml()
;(formRef.value as any).validate(async (valid) => {
if (valid) {
const res = await setExampApi({
data: form
})
if (res) {
Message.success(form.id ? '编辑成功' : '新增成功')
emit('success', form.id ? 'edit' : 'add')
}
} else {
console.log('error submit!!')
return false
}
})
} catch (err) {
console.log(err)
} finally {
subLoading.value = false
}
}
function close() {
push('/example-demo/example-page')
}
getDet()
</script>

View File

@@ -1,18 +0,0 @@
export interface InfoWriteParams {
title: string
id?: string
author: string
content: string
importance: string
display_time: string
pageviews: number
}
export interface InfoWriteRules {
title?: any[]
author?: any[]
content?: any[]
importance?: any[]
display_time?: any[]
pageviews?: any[]
}

View File

@@ -1,11 +0,0 @@
<template>
<info-write @success="success" />
</template>
<script setup lang="ts" name="ExampleAdd">
import InfoWrite from './components/InfoWrite.vue'
import bus from '@/vue-bus'
function success(type: string) {
bus.$emit('success', type)
}
</script>

View File

@@ -1,10 +0,0 @@
<template>
<detail :id="id" />
</template>
<script setup lang="ts" name="ExampleDetail">
import Detail from './components/Detail.vue'
import { useRoute } from 'vue-router'
const { query } = useRoute()
const id = query.id as string
</script>

View File

@@ -1,17 +0,0 @@
<template>
<info-write :id="id" @success="success" />
</template>
<script setup lang="ts">
import InfoWrite from './components/InfoWrite.vue'
import bus from '@/vue-bus'
import { useRoute } from 'vue-router'
const { query } = useRoute()
const id = query.id as string
// 成功之后的回调
function success(type: string) {
// 由于使用的是页面跳转所以只能通过vueBus去进行通信
bus.$emit('success', type)
}
</script>

View File

@@ -1,139 +0,0 @@
<template>
<div>
<div class="search__example--wrap">
<com-search :data="searchData" @search-submit="searchSubmit" @reset-submit="resetSubmit" />
</div>
<div class="button__example--wrap">
<el-button type="primary" icon="el-icon-circle-plus-outline" @click="open(null)">
新增
</el-button>
<el-button type="danger" icon="el-icon-delete" @click="dels">删除</el-button>
</div>
<com-table
v-loading="loading"
selection
:columns="columns"
:data="tableData"
:pagination="{
currentPage: defaultParams.pageIndex,
total: total,
onSizeChange: handleSizeChange,
onCurrentChange: handleCurrentChange
}"
@selection-change="handleSelectionChange"
>
<template #importance="scope">
<el-tag
:type="
scope.row.importance === 3
? 'success'
: scope.row.importance === 2
? 'warning'
: 'danger'
"
>
{{ scope.row.importance === 3 ? '重要' : scope.row.importance === 2 ? '良好' : '一般' }}
</el-tag>
</template>
<template #action="scope">
<el-button type="primary" size="mini" @click="open(scope.row)">编辑</el-button>
<el-button type="success" size="mini" @click="open(scope.row, 'Detail')">查看</el-button>
<el-button type="danger" size="mini" @click="dels(scope.row)">删除</el-button>
</template>
</com-table>
</div>
</template>
<script setup lang="ts" name="ExampleDialog">
import { onBeforeUnmount } from 'vue'
import { getExampleListApi, delsExampApi } from './api'
import { useWork } from '@/hooks/work/useWork'
import { useRouter } from 'vue-router'
const { push } = useRouter()
import bus from '@/vue-bus'
const {
defaultParams,
tableData,
loading,
total,
handleSizeChange,
handleCurrentChange,
handleSelectionChange,
getList,
searchSubmit,
resetSubmit,
refreshTable,
dels
} = useWork({
listFun: getExampleListApi,
delFun: delsExampApi
})
const searchData = [
{
label: '标题',
value: '',
itemType: 'input',
field: 'title',
placeholder: '请输入标题',
clearable: true
}
]
const columns = [
{
field: 'title',
label: '标题',
showOverflowTooltip: true
},
{
field: 'author',
label: '作者'
},
{
field: 'display_time',
label: '创建时间'
},
{
field: 'importance',
label: '重要性',
slots: {
default: 'importance'
}
},
{
field: 'pageviews',
label: '阅读数'
},
{
field: 'action',
label: '操作',
width: '220px',
slots: {
default: 'action'
}
}
]
function open(row: Nullable<IObj>, component?: string) {
push(
!row
? `/example-demo/example-add`
: component
? `/example-demo/example-detail?id=${row.id}`
: `/example-demo/example-edit?id=${row.id}`
)
}
getList()
bus.$on('success', (type: string) => {
refreshTable(type)
})
onBeforeUnmount(() => {
bus.$off('success')
})
</script>

View File

@@ -1,32 +0,0 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title=" 引导页对于一些第一次进入项目的人很有用,你可以简单介绍下项目的功能。引导页基于 intro.js"
type="info"
style="margin-bottom: 20px"
/>
<el-button type="primary" @click.prevent.stop="guide"> 开始引导 </el-button>
</div>
</template>
<script setup lang="ts" name="Guide">
import { onMounted } from 'vue'
import { useIntro } from '@/hooks/web/useIntro'
const { intro } = useIntro()
import steps from './steps'
function guide() {
intro.start()
}
onMounted(() => {
intro.addSteps(steps as any[]).setOptions({
prevLabel: '上一步',
nextLabel: '下一步',
skipLabel: '跳过',
doneLabel: '结束'
})
})
</script>

View File

@@ -1,40 +0,0 @@
const steps = [
{
element: '#sidebar__wrap',
title: '菜单栏',
intro: '以路由的结构渲染的菜单栏',
position: 'right'
},
{
element: '#hamburger-container',
title: '展开缩收',
intro: '用于展开和缩放菜单栏',
position: 'bottom'
},
{
element: '#breadcrumb-container',
title: '面包屑',
intro: '用于记录当前路由结构',
position: 'bottom'
},
{
element: '#screenfull-container',
title: '是否全屏',
intro: '用于设置是否全屏',
position: 'bottom'
},
{
element: '#user-container',
title: '用户信息',
intro: '用于展示用户',
position: 'bottom'
},
{
element: '#tag-container',
title: '标签页',
intro: '用于记录路由历史记录',
position: 'bottom'
}
]
export default steps

View File

@@ -1,58 +0,0 @@
<template>
<div class="icons-container">
<div v-for="item of svgIcons" :key="item" v-clipboard="generateIconCode(item)">
<el-tooltip placement="top" :content="generateIconCode(item)">
<div class="icon-item">
<svg-icon :icon-class="item" class-name="disabled" />
<span>{{ item }}</span>
</div>
</el-tooltip>
</div>
</div>
</template>
<script lang="ts">
import svgIcons from './svg-icons'
import { defineComponent } from 'vue'
export default defineComponent({
// name: 'Icons',
setup() {
function generateIconCode(symbol: string) {
return `<svg-icon icon-class="${symbol}" />`
}
return {
svgIcons,
generateIconCode
}
}
})
</script>
<style lang="less" scoped>
.icons-container {
overflow: hidden;
background: #fff;
.icon-item {
float: left;
width: 100px;
height: 85px;
margin: 20px;
font-size: 30px;
color: #24292e;
text-align: center;
cursor: pointer;
}
span {
display: block;
margin-top: 10px;
font-size: 16px;
}
.disabled {
pointer-events: none;
}
}
</style>

View File

@@ -1,9 +0,0 @@
const modules = import.meta.glob('../../assets/icons/*.svg')
const svgIcons: string[] = []
for (const key in modules) {
svgIcons.push(key.split('../../assets/icons/')[1].split('.')[0])
}
export default svgIcons

View File

@@ -1,9 +0,0 @@
import fetch from '@/axios-config'
export const loginApi = ({ data }: FetchConfig) => {
return fetch({ url: '/user/login', method: 'post', data })
}
export const getRoleDetApi = ({ params }: FetchConfig) => {
return fetch({ url: '/role/detail', method: 'get', params })
}

View File

@@ -5,7 +5,6 @@ import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
import { usePermissionStore } from '@/store/modules/permission'
import { useAppStore } from '@/store/modules/app'
import { ElNotification } from 'element-plus'
import { loginApi, getRoleDetApi } from './api'
import { useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache()
@@ -50,25 +49,14 @@ export default defineComponent({
try {
loading.value = true
// 模拟登录接口之后返回角色信息
const res: IObj = await loginApi({ data: form })
if (res) {
// 获取权限信息
const role = await getRoleDetApi({
params: {
id: res.data.roleId
}
wsCache.set(appStore.getUserInfo, form)
permissionStore.generateRoutes().then(() => {
permissionStore.getAddRouters.forEach(async (route) => {
await addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
})
if (role) {
wsCache.set(appStore.getUserInfo, Object.assign(form, role.data))
permissionStore.generateRoutes().then(() => {
permissionStore.getAddRouters.forEach(async (route) => {
await addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
})
permissionStore.setIsAddRouters(true)
push({ path: redirect.value || '/' })
})
}
}
permissionStore.setIsAddRouters(true)
push({ path: redirect.value || '/' })
})
} finally {
loading.value = false
}

View File

@@ -1,13 +0,0 @@
import fetch from '@/axios-config'
export const getRoleListApi = ({ params }: any) => {
return fetch({ url: '/role/list', method: 'get', params })
}
export const setRoleApi = ({ data }: any) => {
return fetch({ url: '/role/save', method: 'post', data })
}
export const getRoleDetApi = ({ params }: any) => {
return fetch({ url: '/role/detail', method: 'get', params })
}

View File

@@ -1,201 +0,0 @@
<template>
<div>
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
<el-row>
<el-col :span="24">
<el-form-item prop="roleName" label="角色名">
<el-input v-model="form.roleName" disabled placeholder="请输入角色名" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="角色权限">
<el-tree
ref="tree"
:check-strictly="false"
:data="routesData as any"
:props="defaultProps as any"
show-checkbox
accordion
node-key="path"
highlight-current
class="permission-tree"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="dialong__button--wrap">
<el-button @click="close">取消</el-button>
<el-button :loading="subLoading" type="primary" @click="setListData">保存</el-button>
</div>
</div>
</template>
<script setup lang="ts" name="InfoWrite">
import { PropType, computed, nextTick, reactive, ref } from 'vue'
import path from 'path-browserify'
import { setRoleApi, getRoleDetApi } from '../api'
import { asyncRouterMap } from '@/router'
import { isExternal } from '@/utils/validate'
import { Message } from '_c/Message'
import { AppRouteRecordRaw } from '@/router/types'
const requiredRule: {
required: boolean
message: string
} = {
required: true,
message: '该项为必填项'
}
const props = defineProps({
info: {
type: Object as PropType<Nullable<IObj>>,
default: () => null
}
})
const emit = defineEmits(['success', 'close'])
const tree = ref<Nullable<HTMLElement>>(null)
const formRef = ref<Nullable<HTMLElement>>(null)
const subLoading = ref<boolean>(false)
const form = reactive<IObj>({
id: '', // id
roleName: '', // 角色名
checkedNodes: [], // 被选中的节点
checkedkeys: [] // 被选中的keys
})
const rules = reactive<IObj>({
roleName: [requiredRule]
})
const routes = ref<IObj>([])
const defaultProps = reactive<IObj>({
children: 'children',
label: 'title'
})
const routesData = computed(() => routes.value)
async function getDet() {
if (props.info) {
const id = props.info.id
try {
const res: any = await getRoleDetApi({
params: {
id: id
}
})
if (res) {
for (const key in form) {
form[key] = res.data[key]
}
nextTick(() => {
;(tree.value as any).setCheckedKeys(form.checkedkeys)
})
}
} catch (e) {
console.log(e)
}
}
}
// 新增或者编辑
function setListData() {
try {
subLoading.value = true
;(formRef.value as any).validate(async (valid) => {
if (valid) {
// 获取所有被选中节点,由于是前端渲染,所以只要保存一维数组就行
form.checkedNodes = (tree.value as any).getCheckedNodes(false, true)
console.log(JSON.stringify(form.checkedNodes))
// 获取所有被选中的keys便于渲染是否选中
form.checkedkeys = (tree.value as any).getCheckedKeys()
console.log(JSON.stringify(form.checkedkeys))
const res = await setRoleApi({
data: form
})
if (res) {
Message.success(
form.id ? '编辑成功,请重新退出登录后查看效果' : '新增成功,请重新退出登录后查看效果'
)
emit('success', form.id ? 'edit' : 'add')
}
} else {
console.log('error submit!!')
return false
}
})
} catch (err) {
console.log(err)
} finally {
subLoading.value = false
}
}
function generateRoutes(routes: AppRouteRecordRaw[], basePath = '/') {
const res: AppRouteRecordRaw[] = []
for (let route of routes) {
// skip some route
if (route.meta && route.meta.hidden) {
continue
}
const onlyOneShowingChild = onlyOneShowingChildFn(
route.children,
route,
path.resolve(basePath, route.path)
)
if (route.children && onlyOneShowingChild && !(route.meta && route.meta.alwaysShow)) {
route = onlyOneShowingChild
}
const data = {
path: isExternal(route.path) ? route.path : path.resolve(basePath, route.path),
title: route.meta && route.meta.title,
name: route.name
}
// recursive child routes
if (route.children) {
;(data as any).children = generateRoutes(route.children, data.path)
}
res.push(data as any)
}
return res
}
function onlyOneShowingChildFn(
children: AppRouteRecordRaw[] = [],
parent: AppRouteRecordRaw,
basePath: string
) {
let onlyOneChild: Nullable<AppRouteRecordRaw | any> = null
const showingChildren = children.filter((item) => !(item.meta && item.meta.hidden))
// When there is only one child route, the child route is displayed by default
if (showingChildren.length === 1) {
onlyOneChild = showingChildren[0]
onlyOneChild.path = isExternal(onlyOneChild.path)
? onlyOneChild.path
: path.resolve(basePath, onlyOneChild.path)
return onlyOneChild
}
// Show parent if there are no child route to display
if (showingChildren.length === 0) {
onlyOneChild = { ...parent, path: '', noShowingChildren: true }
return onlyOneChild
}
return false
}
function close() {
emit('close')
}
const oldRoutes = [...asyncRouterMap]
routes.value = generateRoutes(oldRoutes)
getDet()
</script>

View File

@@ -1,225 +0,0 @@
<template>
<div>
<el-form ref="formRef" :model="form" :rules="rules" label-width="130px">
<el-row>
<el-col :span="24">
<el-form-item prop="roleName" label="角色名">
<el-input v-model="form.roleName" disabled placeholder="请输入角色名" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="角色权限">
<el-tree
ref="tree"
:check-strictly="false"
:expand-on-click-node="false"
:data="routesData as any"
:props="defaultProps as any"
accordion
node-key="path"
highlight-current
class="permission-tree"
@node-click="handleNodeClick"
/>
</el-form-item>
</el-col>
<el-col v-if="seletTreeData" :span="12">
<el-form-item label="title">
<el-input v-model="seletTreeData.title" />
</el-form-item>
<el-form-item label="component">
<el-input v-model="seletTreeData.component" />
</el-form-item>
<el-form-item label="redirect">
<el-input v-model="seletTreeData.redirect" />
</el-form-item>
<el-form-item label="activeMenu">
<el-input v-model="seletTreeData.meta.activeMenu" />
</el-form-item>
<el-form-item label="name">
<el-input v-model="seletTreeData.name" />
</el-form-item>
<el-form-item label="icon">
<el-input v-model="seletTreeData.meta.icon" />
</el-form-item>
<el-form-item label="hidden">
<el-switch v-model="seletTreeData.meta.hidden" />
</el-form-item>
<el-form-item label="alwaysShow">
<el-switch v-model="seletTreeData.meta.alwaysShow" />
</el-form-item>
<el-form-item label="noCache">
<el-switch v-model="seletTreeData.meta.noCache" />
</el-form-item>
<el-form-item label="breadcrumb">
<el-switch v-model="seletTreeData.meta.breadcrumb" />
</el-form-item>
<el-form-item label="affix">
<el-switch v-model="seletTreeData.meta.affix" />
</el-form-item>
<el-form-item label="noTagsView">
<el-switch v-model="seletTreeData.meta.noTagsView" />
</el-form-item>
<el-form-item label="showMainRoute">
<el-switch v-model="seletTreeData.meta.showMainRoute" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="dialong__button--wrap">
<el-button @click="close">取消</el-button>
<el-button :loading="subLoading" type="primary" @click="setListData">保存</el-button>
</div>
</div>
</template>
<script setup lang="ts" name="InfoWrite2">
import { PropType, computed, nextTick, reactive, ref } from 'vue'
import { setRoleApi, getRoleDetApi } from '../api'
import { Message } from '_c/Message'
import { AppRouteRecordRaw } from '@/router/types'
const requiredRule: {
required: boolean
message: string
} = {
required: true,
message: '该项为必填项'
}
const props = defineProps({
info: {
type: Object as PropType<Nullable<IObj>>,
default: () => null
}
})
const emit = defineEmits(['success', 'close'])
const tree = ref<Nullable<HTMLElement>>(null)
const formRef = ref<Nullable<HTMLElement>>(null)
const subLoading = ref<boolean>(false)
const form = reactive<IObj>({
id: '', // id
roleName: '', // 角色名
checkedNodes: [], // 被选中的节点
checkedkeys: [] // 被选中的keys
})
const rules = reactive<IObj>({
roleName: [requiredRule]
})
const routes = ref<IObj>([])
const defaultProps = reactive<IObj>({
children: 'children',
label: 'title'
})
const seletTreeData = ref<Nullable<IObj>>(null)
const routesData = computed(() => routes.value)
async function getDet() {
if (props.info) {
const id = props.info.id
try {
const res: any = await getRoleDetApi({
params: {
id: id
}
})
if (res) {
console.log(res)
for (const key in form) {
form[key] = res.data[key]
}
routes.value = generateRoutes(form.checkedNodes)
nextTick(() => {
;(tree.value as any).setCheckedKeys(form.checkedkeys)
})
}
} catch (e) {
console.log(e)
}
}
}
// 树形点击
function handleNodeClick(data: IObj) {
seletTreeData.value = data
}
// 新增或者编辑
function setListData() {
try {
subLoading.value = true
;(formRef.value as any).validate(async (valid) => {
if (valid) {
console.log(routesData.value)
// 获取所有被选中节点
// const checkedNodes = this.$refs.tree.getCheckedNodes(false, true).filter(v => {
// if (v.path.includes('/')) return v
// })
// // 获取所有被选中的keys便于渲染是否选中
// this.form.checkedkeys = this.$refs.tree.getCheckedKeys()
// console.log(JSON.stringify(this.form.checkedkeys))
// this.form.checkedNodes = this.getFilterNodes(checkedNodes)
// console.log(JSON.stringify(this.form.checkedNodes))
const res = await setRoleApi({
data: Object.assign(form, { checkedNodes: routesData.value })
})
if (res) {
Message.success(
form.id ? '编辑成功,请重新退出登录后查看效果' : '新增成功,请重新退出登录后查看效果'
)
emit('success', form.id ? 'edit' : 'add')
}
} else {
console.log('error submit!!')
return false
}
})
} catch (err) {
console.log(err)
} finally {
subLoading.value = false
}
}
function close() {
emit('close')
}
// 树形渲染过滤
function generateRoutes(routes: AppRouteRecordRaw[]) {
const res: AppRouteRecordRaw[] = []
for (const route of routes) {
const data: AppRouteRecordRaw = {
path: route.path,
name: route.name,
redirect: route.redirect || '',
title: (route as any).title || (route.meta && route.meta.title),
component: (route as any).component || '',
meta: {
title: (route as any).title || (route.meta && route.meta.title),
alwaysShow: route.meta && route.meta.alwaysShow,
hidden: route.meta && route.meta.hidden,
icon: route.meta && route.meta.icon,
noCache: route.meta && route.meta.noCache,
breadcrumb: route.meta && route.meta.breadcrumb,
affix: route.meta && route.meta.affix,
noTagsView: route.meta && route.meta.noTagsView,
activeMenu: route.meta && route.meta.activeMenu,
showMainRoute: route.meta && route.meta.showMainRoute
}
}
// recursive child routes
if (route.children) {
data.children = generateRoutes(route.children)
}
res.push(data)
}
return res
}
getDet()
</script>

View File

@@ -1,124 +0,0 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="由于是模拟数据,所以只提供了两种不同权限的角色,开发者可根据实际情况自行改造结合。"
type="info"
style="margin-bottom: 20px"
/>
<div class="search__example--wrap">
<com-search :data="searchData" @search-submit="searchSubmit" @reset-submit="resetSubmit" />
</div>
<com-table
v-loading="loading"
:columns="columns"
:data="tableData"
:pagination="{
currentPage: defaultParams.pageIndex,
total: total,
onSizeChange: handleSizeChange,
onCurrentChange: handleCurrentChange
}"
>
<template #remark="scope">
<span>模拟</span>
<el-tag
:type="scope.row.roleName === 'admin' ? 'success' : 'warning'"
style="margin: 0 15px"
>
{{ scope.row.roleName === 'admin' ? '前端' : '后端' }}
</el-tag>
<span>角色</span>
</template>
<template #action="scope">
<el-button
type="primary"
size="mini"
@click="open(scope.row, scope.row.roleName === 'admin' ? 'InfoWrite' : 'InfoWrite2')"
>
编辑
</el-button>
</template>
</com-table>
<com-dialog v-model="dialogVisible" :title="dialogTitle">
<info-write
v-if="comName === 'InfoWrite' && dialogVisible"
:info="rowData"
@close="toggleVisible"
@success="refreshTable"
/>
<info-write2
v-if="comName === 'InfoWrite2' && dialogVisible"
:info="rowData"
@close="toggleVisible"
@success="refreshTable"
/>
</com-dialog>
</div>
</template>
<script setup lang="ts" name="Role">
import { getRoleListApi } from './api'
import { useWork } from '@/hooks/work/useWork'
import InfoWrite from './components/InfoWrite.vue'
import InfoWrite2 from './components/InfoWrite2.vue'
const {
defaultParams,
tableData,
loading,
total,
dialogVisible,
dialogTitle,
comName,
rowData,
handleSizeChange,
handleCurrentChange,
toggleVisible,
getList,
searchSubmit,
resetSubmit,
open,
refreshTable
} = useWork({
listFun: getRoleListApi
})
const searchData = [
{
label: '角色名',
value: '',
itemType: 'input',
field: 'roleName',
placeholder: '请输入角色名',
clearable: true
}
]
const columns = [
{
field: 'roleName',
label: '角色名'
},
{
label: '备注',
slots: {
default: 'remark'
}
},
{
field: 'action',
label: '操作',
width: '80px',
slots: {
default: 'action'
}
}
]
getList()
</script>

View File

@@ -1,5 +0,0 @@
import fetch from '@/axios-config'
export const getUserListApi = ({ params }: any) => {
return fetch({ url: '/user/list', method: 'get', params })
}

View File

@@ -1,90 +0,0 @@
<template>
<div>
<el-alert
effect="dark"
:closable="false"
title="由于是模拟数据,所以只提供了两种不同权限的帐号,开发者可根据实际情况自行改造结合。"
type="info"
style="margin-bottom: 20px"
/>
<div class="search__example--wrap">
<com-search :data="searchData" @search-submit="searchSubmit" @reset-submit="resetSubmit" />
</div>
<com-table
v-loading="loading"
:columns="columns"
:data="tableData"
:pagination="{
currentPage: defaultParams.pageIndex,
total: total,
onSizeChange: handleSizeChange,
onCurrentChange: handleCurrentChange
}"
>
<template #remark="scope">
<span>模拟</span>
<el-tag
:type="scope.row.userName === 'admin' ? 'success' : 'warning'"
style="margin: 0 15px"
>
{{ scope.row.userName === 'admin' ? '前端' : '后端' }}
</el-tag>
<span>控制路由权限</span>
</template>
</com-table>
</div>
</template>
<script setup lang="ts" name="User">
import { getUserListApi } from './api'
import { useWork } from '@/hooks/work/useWork'
const {
defaultParams,
tableData,
loading,
total,
handleSizeChange,
handleCurrentChange,
getList,
searchSubmit,
resetSubmit
} = useWork({
listFun: getUserListApi
})
const searchData = [
{
label: '帐号',
value: '',
itemType: 'input',
field: 'userName',
placeholder: '请输入帐号',
clearable: true
}
]
const columns = [
{
field: 'userName',
label: '帐号'
},
{
field: 'password',
label: '密码'
},
{
field: 'role',
label: '角色'
},
{
label: '备注',
slots: {
default: 'remark'
}
}
]
getList()
</script>

View File

@@ -1,61 +0,0 @@
<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 setup lang="ts" name="BasicTable">
import { ref } from 'vue'
const columns = [
{
field: 'date',
label: '日期'
},
{
field: 'name',
label: '姓名'
},
{
field: '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 弄'
}
]
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 1000)
</script>
<style></style>

View File

@@ -1,61 +0,0 @@
<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 setup lang="ts" name="BorderTable">
import { ref } from 'vue'
const columns = [
{
field: 'date',
label: '日期'
},
{
field: 'name',
label: '姓名'
},
{
field: '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 弄'
}
]
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 1000)
</script>
<style></style>

View File

@@ -1,91 +0,0 @@
<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 setup lang="ts" name="CustomHeader">
import { ref } from 'vue'
const columns = [
{
field: 'date',
label: '日期'
},
{
field: 'name',
label: '姓名'
},
{
field: '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 弄'
}
]
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 1000)
const search = ref<string>('')
function handleEdit(index: number, row: any) {
console.log(index, row)
}
function handleDelete(index: number, row: any) {
console.log(index, row)
}
</script>
<style></style>

View File

@@ -1,68 +0,0 @@
<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 setup lang="ts" name="CustomIndex">
import { ref } from 'vue'
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 弄'
}
]
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 1000)
const columns = ref<any[]>([
{
field: 'index',
type: 'index',
index: (index: number) => {
return index * 2
}
},
{
field: 'date',
label: '日期'
},
{
field: 'name',
label: '姓名'
},
{
field: 'address',
label: '地址'
}
])
</script>
<style></style>

View File

@@ -1,125 +0,0 @@
<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 setup lang="ts" name="ExpandRow">
import { ref } from 'vue'
const columns = [
{
field: 'id',
type: 'expand',
slots: {
default: 'id'
}
},
{
field: 'id',
label: '商品ID'
},
{
field: 'name',
label: '商品名称'
},
{
field: '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'
}
]
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 1000)
</script>
<style lang="less" scoped>
:deep(.demo-table-expand) {
font-size: 0;
label {
width: 90px;
color: #99a9bf;
}
.el-form-item {
width: 50%;
margin-right: 0;
margin-bottom: 0;
}
}
</style>

View File

@@ -1,149 +0,0 @@
<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 setup lang="ts" name="FixedColumnHeader">
import { ref } from 'vue'
const columns = [
{
field: 'date',
label: '日期',
fixed: true,
width: '150'
},
{
field: 'name',
label: '姓名',
width: '120'
},
{
field: 'province',
label: '省份',
width: '120'
},
{
field: 'city',
label: '市区',
width: '120'
},
{
field: 'address',
label: '地址',
width: '300'
},
{
field: 'zip',
label: '邮编',
width: '120'
},
{
field: '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
}
]
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 1000)
function handleClick(row: any) {
console.log(row)
}
</script>
<style></style>

View File

@@ -1,110 +0,0 @@
<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 setup lang="ts" name="FixedColumn">
import { ref } from 'vue'
const columns = [
{
field: 'date',
label: '日期',
fixed: true,
width: '150'
},
{
field: 'name',
label: '姓名',
width: '120'
},
{
field: 'province',
label: '省份',
width: '120'
},
{
field: 'city',
label: '市区',
width: '120'
},
{
field: 'address',
label: '地址',
width: '300'
},
{
field: 'zip',
label: '邮编',
width: '120'
},
{
field: '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
}
]
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 1000)
function handleClick(row: any) {
console.log(row)
}
</script>
<style></style>

View File

@@ -1,81 +0,0 @@
<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" height="250" border />
</div>
</template>
<script setup lang="ts" name="FixedHeader">
import { ref } from 'vue'
const columns = [
{
field: 'date',
label: '日期'
},
{
field: 'name',
label: '姓名'
},
{
field: '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 弄'
},
{
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 弄'
}
]
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 1000)
</script>
<style></style>

View File

@@ -1,147 +0,0 @@
<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 setup lang="ts" name="FluidHeight">
import { ref } from 'vue'
const columns = [
{
field: 'date',
label: '日期',
fixed: true,
width: '150'
},
{
field: 'name',
label: '姓名',
width: '120'
},
{
field: 'province',
label: '省份',
width: '120'
},
{
field: 'city',
label: '市区',
width: '120'
},
{
field: 'address',
label: '地址',
width: '300'
},
{
field: 'zip',
label: '邮编',
width: '120'
},
{
field: 'action',
label: '操作',
width: '100',
fixed: 'right',
slots: {
default: 'action'
}
}
]
const tableData = ref<IObj[]>([
{
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
}, 1000)
function deleteRow(index: number) {
tableData.value.splice(index, 1)
}
</script>
<style></style>

View File

@@ -1,151 +0,0 @@
<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"
:span-method="arraySpanMethod"
border
/>
<com-table
v-loading="loading"
:columns="columns1"
:data="tableData"
:span-method="objectSpanMethod"
border
style="margin-top: 20px"
/>
</div>
</template>
<script setup lang="ts" name="MergeTable">
import { ref } from 'vue'
const columns = [
{
field: 'id',
label: 'ID'
},
{
field: 'name',
label: '姓名'
},
{
field: 'amount1',
label: '数值1',
sortable: true
},
{
field: 'amount2',
label: '数值2',
sortable: true
},
{
field: 'amount3',
label: '数值4',
sortable: true
}
]
const columns1 = [
{
field: 'id',
label: 'ID'
},
{
field: 'name',
label: '姓名'
},
{
field: 'amount1',
label: '数值1'
},
{
field: 'amount2',
label: '数值2'
},
{
field: '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
}
]
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 1000)
function arraySpanMethod({ rowIndex, columnIndex }: any) {
if (rowIndex % 2 === 0) {
if (columnIndex === 0) {
return [1, 2]
} else if (columnIndex === 1) {
return [0, 0]
}
}
}
function objectSpanMethod({ rowIndex, columnIndex }: any) {
if (columnIndex === 0) {
if (rowIndex % 2 === 0) {
return {
rowspan: 2,
colspan: 1
}
} else {
return {
rowspan: 0,
colspan: 0
}
}
}
}
</script>
<style></style>

View File

@@ -1,145 +0,0 @@
<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 setup lang="ts" name="MultiHeader">
import { ref } from 'vue'
const columns = [
{
field: 'date',
label: '日期',
fixed: true,
width: '150'
},
{
label: '配送信息',
children: [
{
field: 'name',
label: '姓名',
width: '120'
},
{
label: '地址',
children: [
{
field: 'province',
label: '省份',
width: '120'
},
{
field: 'city',
label: '市区',
width: '120'
},
{
field: 'address',
label: '地址',
slots: {
default: 'address'
}
},
{
field: 'zip',
label: '邮编',
width: '120'
}
]
}
]
},
{
field: 'action',
label: '操作',
width: '100',
slots: {
default: 'action'
}
}
]
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
}, 1000)
function deleteRow(index: number) {
tableData.value.splice(index, 1)
}
</script>
<style></style>

View File

@@ -1,90 +0,0 @@
<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 setup lang="ts" name="MultipleChoice">
import { ref, unref } from 'vue'
const columns = [
{
field: 'date',
label: '日期'
},
{
field: 'name',
label: '姓名'
},
{
field: '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 弄'
}
]
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 1000)
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)
}
</script>
<style></style>

View File

@@ -1,79 +0,0 @@
<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"
:pagination="{
currentPage: 1,
total: 400,
onSizeChange: handleSizeChange,
onCurrentChange: handleCurrentChange
}"
/>
</div>
</template>
<script setup lang="ts" name="PageTable">
import { ref } from 'vue'
const columns = [
{
field: 'date',
label: '日期'
},
{
field: 'name',
label: '姓名'
},
{
field: '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 弄'
}
]
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 1000)
function handleSizeChange(val: number) {
console.log(val)
}
function handleCurrentChange(val: number) {
console.log(val)
}
</script>
<style></style>

View File

@@ -1,124 +0,0 @@
<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') as any"
disable-transitions
>{{ scope.row.tag }}</el-tag
>
</template>
</com-table>
</div>
</template>
<script setup lang="ts" name="ScreenTable">
import { ref, unref } from '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: '公司'
}
]
const filterTable = ref<HTMLElement | null>(null)
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 1000)
const columns = ref<any[]>([
{
field: '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
},
{
field: 'name',
label: '姓名',
sortable: true
},
{
field: 'address',
label: '地址'
},
{
field: '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
}
</script>
<style></style>

View File

@@ -1,82 +0,0 @@
<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 setup lang="ts" name="SingleChoice">
import { ref, unref } from 'vue'
const columns = [
{
field: 'date',
label: '日期'
},
{
field: 'name',
label: '姓名'
},
{
field: '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 弄'
}
]
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 1000)
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)
}
</script>
<style></style>

View File

@@ -1,69 +0,0 @@
<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 setup lang="ts" name="SortTable">
import { ref } from 'vue'
const columns = [
{
field: 'date',
label: '日期',
sortable: true
},
{
field: 'name',
label: '姓名',
sortable: true
},
{
field: '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 弄'
}
]
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 1000)
</script>
<style></style>

View File

@@ -1,85 +0,0 @@
<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 setup lang="ts" name="StateTable">
import { ref } from 'vue'
const columns = [
{
field: 'date',
label: '日期'
},
{
field: 'name',
label: '姓名'
},
{
field: '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 弄'
}
]
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 1000)
function tableRowClassName({ rowIndex }: any) {
if (rowIndex === 1) {
return 'warning-row'
} else if (rowIndex === 3) {
return 'success-row'
}
return ''
}
</script>
<style lang="less" scoped>
:deep(.el-table) {
.warning-row {
background: oldlace;
}
.success-row {
background: #f0f9eb;
}
}
</style>

View File

@@ -1,61 +0,0 @@
<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 setup lang="ts" name="StripeTable">
import { ref } from 'vue'
const columns = [
{
field: 'date',
label: '日期'
},
{
field: 'name',
label: '姓名'
},
{
field: '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 弄'
}
]
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 1000)
</script>
<style></style>

View File

@@ -1,148 +0,0 @@
<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 setup lang="ts" name="TotalTable">
import { ref } from 'vue'
const columns = [
{
field: 'id',
label: 'ID'
},
{
field: 'name',
label: '姓名'
},
{
field: 'amount1',
label: '数值1',
sortable: true
},
{
field: 'amount2',
label: '数值2',
sortable: true
},
{
field: 'amount3',
label: '数值4',
sortable: true
}
]
const columns1 = [
{
field: 'id',
label: 'ID'
},
{
field: 'name',
label: '姓名'
},
{
field: 'amount1',
label: '数值1'
},
{
field: 'amount2',
label: '数值2'
},
{
field: '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
}
]
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 1000)
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
}
</script>
<style></style>

View File

@@ -1,163 +0,0 @@
<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 setup lang="ts" name="TreeAndLoad">
import { ref } from 'vue'
const columns = [
{
field: 'date',
label: '日期',
sortable: true
},
{
field: 'name',
label: '姓名',
sortable: true
},
{
field: 'address',
label: '地址'
}
]
const columns1 = [
{
field: 'date',
label: '日期'
},
{
field: 'name',
label: '姓名'
},
{
field: '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 弄'
}
]
const loading = ref<boolean>(true)
setTimeout(() => {
loading.value = false
}, 1000)
function load(_: any, __: any, resolve: Function) {
setTimeout(() => {
resolve([
{
id: 31,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
},
{
id: 32,
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
}
])
}, 1000)
}
</script>
<style></style>

View File

@@ -1,13 +0,0 @@
// 通过mitt实现vue-bus通信
import mitt from 'mitt'
const bus: any = {}
const emitter = mitt()
bus.$on = emitter.on
bus.$off = emitter.off
bus.$emit = emitter.emit
export default bus