feat: 部分组件重构完成
This commit is contained in:
parent
0f5c55c36d
commit
3d9622978d
|
@ -6,12 +6,17 @@ declare module 'vue' {
|
|||
export interface GlobalComponents {
|
||||
404: typeof import('C:/Users/Saber/Documents/HBuilderProjects/vue-element-plus-admin/src/components/Error/404.vue')['default']
|
||||
CountTo: typeof import('C:/Users/Saber/Documents/HBuilderProjects/vue-element-plus-admin/src/components/CountTo/index.vue')['default']
|
||||
Dialog: typeof import('C:/Users/Saber/Documents/HBuilderProjects/vue-element-plus-admin/src/components/Dialog/index.vue')['default']
|
||||
Echart: typeof import('C:/Users/Saber/Documents/HBuilderProjects/vue-element-plus-admin/src/components/Echart/index.vue')['default']
|
||||
Editor: typeof import('C:/Users/Saber/Documents/HBuilderProjects/vue-element-plus-admin/src/components/Editor/index.vue')['default']
|
||||
ElAlert: typeof import('element-plus/es')['ElAlert']
|
||||
ElBacktop: typeof import('element-plus/es')['ElBacktop']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCard: typeof import('element-plus/es')['ElCard']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDrawer: typeof import('element-plus/es')['ElDrawer']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
|
@ -19,21 +24,31 @@ declare module 'vue' {
|
|||
ElForm: typeof import('element-plus/es')['ElForm']
|
||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
|
||||
ElOption: typeof import('element-plus/es')['ElOption']
|
||||
ElRadio: typeof import('element-plus/es')['ElRadio']
|
||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElScrollbar: typeof import('element-plus/es')['ElScrollbar']
|
||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||
ElSubMenu: typeof import('element-plus/es')['ElSubMenu']
|
||||
ElSwitch: typeof import('element-plus/es')['ElSwitch']
|
||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||
ElTimePicker: typeof import('element-plus/es')['ElTimePicker']
|
||||
ElTimeSelect: typeof import('element-plus/es')['ElTimeSelect']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
HelloWorld: typeof import('C:/Users/Saber/Documents/HBuilderProjects/vue-element-plus-admin/src/components/HelloWorld.vue')['default']
|
||||
ParentView: typeof import('C:/Users/Saber/Documents/HBuilderProjects/vue-element-plus-admin/src/components/ParentView/index.vue')['default']
|
||||
Preview: typeof import('C:/Users/Saber/Documents/HBuilderProjects/vue-element-plus-admin/src/components/Preview/index.vue')['default']
|
||||
Qrcode: typeof import('C:/Users/Saber/Documents/HBuilderProjects/vue-element-plus-admin/src/components/Qrcode/index.vue')['default']
|
||||
Redirect: typeof import('C:/Users/Saber/Documents/HBuilderProjects/vue-element-plus-admin/src/components/Redirect/index.vue')['default']
|
||||
Search: typeof import('C:/Users/Saber/Documents/HBuilderProjects/vue-element-plus-admin/src/components/Search/index.vue')['default']
|
||||
SvgIcon: typeof import('C:/Users/Saber/Documents/HBuilderProjects/vue-element-plus-admin/src/components/SvgIcon/index.vue')['default']
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
||||
export { }
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "vue-element-plus-admin",
|
||||
"version": "1.0.0",
|
||||
"description": "一套基于vue3、element-plus、typesScript、vite的后台集成方案",
|
||||
"description": "一套基于vue3、element-plus、typesScript、vite2的后台集成方案",
|
||||
"author": "Archer <502431556@qq.com>",
|
||||
"private": false,
|
||||
"scripts": {
|
||||
|
@ -26,9 +26,11 @@
|
|||
"@element-plus/icons": "^0.0.11",
|
||||
"@vueuse/core": "^6.5.3",
|
||||
"axios": "^0.22.0",
|
||||
"clipboard": "^2.0.8",
|
||||
"echarts": "^5.2.1",
|
||||
"echarts-wordcloud": "^2.0.0",
|
||||
"element-plus": "1.1.0-beta.20",
|
||||
"highlight.js": "^11.2.0",
|
||||
"intro.js": "^4.2.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mockjs": "^1.1.0",
|
||||
|
@ -36,9 +38,11 @@
|
|||
"path-browserify": "^1.0.1",
|
||||
"path-to-regexp": "^6.2.0",
|
||||
"pinia": "^2.0.0-rc.13",
|
||||
"qrcode": "^1.4.4",
|
||||
"qs": "^6.10.1",
|
||||
"vue": "^3.2.16",
|
||||
"vue-router": "^4.0.11",
|
||||
"wangeditor": "^4.7.9",
|
||||
"web-storage-cache": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
<template>
|
||||
<router-view class="app" :class="{ grey__mode: greyMode }" />
|
||||
<el-config-provider :locale="zhCn">
|
||||
<router-view class="app" :class="{ grey__mode: greyMode }" />
|
||||
</el-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="App">
|
||||
import { computed } from 'vue'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
const appStore = useAppStore()
|
||||
import zhCn from 'element-plus/lib/locale/lang/zh-cn'
|
||||
|
||||
const greyMode = computed(() => appStore.getGreyMode)
|
||||
</script>
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 4.0 KiB |
|
@ -15,7 +15,9 @@ const emit = defineEmits(['mounted', 'callback'])
|
|||
|
||||
defineExpose({
|
||||
pauseResume,
|
||||
reset
|
||||
reset,
|
||||
start,
|
||||
pause
|
||||
})
|
||||
|
||||
const state = reactive<{
|
||||
|
|
|
@ -0,0 +1,193 @@
|
|||
<template>
|
||||
<el-dialog
|
||||
ref="dialogRef"
|
||||
v-bind="getBindValue"
|
||||
:fullscreen="fullscreen"
|
||||
destroy-on-close
|
||||
lock-scroll
|
||||
:close-on-click-modal="false"
|
||||
top="10vh"
|
||||
>
|
||||
<template #title>
|
||||
<slot name="title">
|
||||
{{ title }}
|
||||
</slot>
|
||||
<svg-icon
|
||||
v-if="showFullscreen"
|
||||
:icon-class="fullscreen ? 'exit-fullscreen' : 'fullscreen'"
|
||||
class-name="dialog__icon"
|
||||
@click="toggleFull"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<!-- 弹窗内容 -->
|
||||
<el-scrollbar
|
||||
:class="
|
||||
fullscreen && slots.footer
|
||||
? 'com-dialog__content--footer'
|
||||
: fullscreen && !slots.footer
|
||||
? 'com-dialog__content--fullscreen'
|
||||
: 'com-dialog__content'
|
||||
"
|
||||
>
|
||||
<div class="content__wrap">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
|
||||
<template v-if="slots.footer" #footer>
|
||||
<slot name="footer"></slot>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="Dialog">
|
||||
import { ref, computed, PropType, nextTick, unref, useAttrs, useSlots } from 'vue'
|
||||
import SvgIcon from '@/components/SvgIcon/index.vue'
|
||||
|
||||
const slots = useSlots()
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String as PropType<string>,
|
||||
default: ''
|
||||
},
|
||||
// 是否显示全屏按钮
|
||||
showFullscreen: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true
|
||||
},
|
||||
// 是否可以拖拽
|
||||
draggable: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true
|
||||
}
|
||||
})
|
||||
const dialogRef = ref<HTMLElement | null>(null)
|
||||
|
||||
const fullscreen = ref<boolean>(false)
|
||||
|
||||
const getBindValue = computed((): any => {
|
||||
const delArr: string[] = ['showFullscreen', 'draggable']
|
||||
const attrs = useAttrs()
|
||||
const obj = { ...attrs, ...props }
|
||||
for (const key in obj) {
|
||||
if (delArr.indexOf(key) !== -1) {
|
||||
delete obj[key]
|
||||
}
|
||||
}
|
||||
return obj
|
||||
})
|
||||
|
||||
function toggleFull(): void {
|
||||
fullscreen.value = !fullscreen.value
|
||||
// 全屏的时候需要重新定义left top
|
||||
if (fullscreen.value && props.draggable) {
|
||||
const dragDom = unref(dialogRef as any).$refs.dialogRef
|
||||
dragDom.style.cssText += `;left:0px;top:0px;`
|
||||
}
|
||||
}
|
||||
|
||||
function initDraggable() {
|
||||
nextTick(() => {
|
||||
const dragDom = unref(dialogRef as any).$refs.dialogRef
|
||||
const dialogHeaderEl = dragDom.querySelector('.el-dialog__header') as HTMLElement
|
||||
dragDom.style.cssText += ';top:0px;'
|
||||
dialogHeaderEl.style.cssText += ';cursor:move;user-select:none;'
|
||||
dialogHeaderEl.onmousedown = (e) => {
|
||||
const disX = e.clientX - dialogHeaderEl.offsetLeft
|
||||
const disY = e.clientY - dialogHeaderEl.offsetTop
|
||||
|
||||
const dragDomWidth = dragDom.offsetWidth
|
||||
const dragDomHeight = dragDom.offsetHeight
|
||||
|
||||
const screenWidth = document.body.clientWidth
|
||||
const screenHeight = document.body.clientHeight
|
||||
|
||||
const minDragDomLeft = dragDom.offsetLeft
|
||||
const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth
|
||||
|
||||
const minDragDomTop = dragDom.offsetTop
|
||||
const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomHeight
|
||||
|
||||
const styleLeftStr = getComputedStyle(dragDom).left
|
||||
const styleTopStr = getComputedStyle(dragDom).top
|
||||
if (!styleLeftStr || !styleTopStr) return
|
||||
let styleLeft: number
|
||||
let styleTop: number
|
||||
|
||||
// Format may be "##%" or "##px"
|
||||
if (styleLeftStr.includes('%')) {
|
||||
styleLeft = +document.body.clientWidth * (+styleLeftStr.replace(/%/g, '') / 100)
|
||||
styleTop = +document.body.clientHeight * (+styleTopStr.replace(/%/g, '') / 100)
|
||||
} else {
|
||||
styleLeft = +styleLeftStr.replace(/px/g, '')
|
||||
styleTop = +styleTopStr.replace(/px/g, '')
|
||||
}
|
||||
|
||||
document.onmousemove = (e) => {
|
||||
let left = e.clientX - disX
|
||||
let top = e.clientY - disY
|
||||
|
||||
// Handle edge cases
|
||||
if (-left > minDragDomLeft) {
|
||||
left = -minDragDomLeft
|
||||
} else if (left > maxDragDomLeft) {
|
||||
left = maxDragDomLeft
|
||||
}
|
||||
if (-top > minDragDomTop) {
|
||||
top = -minDragDomTop
|
||||
} else if (top > maxDragDomTop) {
|
||||
top = maxDragDomTop
|
||||
}
|
||||
|
||||
// Move current element
|
||||
dragDom.style.cssText += `;left:${left + styleLeft}px;top:${top + styleTop}px;`
|
||||
}
|
||||
|
||||
document.onmouseup = () => {
|
||||
document.onmousemove = null
|
||||
document.onmouseup = null
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (props.draggable) {
|
||||
initDraggable()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.dialog__icon {
|
||||
position: absolute;
|
||||
top: 22px;
|
||||
right: 45px;
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
cursor: pointer;
|
||||
transition: color 0.2s;
|
||||
&:hover {
|
||||
color: #409eff;
|
||||
}
|
||||
}
|
||||
.com-dialog__content {
|
||||
.content__wrap {
|
||||
padding-right: 10px;
|
||||
}
|
||||
:deep(.el-scrollbar__wrap) {
|
||||
max-height: 600px; // 最大高度
|
||||
overflow-x: hidden; // 隐藏横向滚动栏
|
||||
}
|
||||
}
|
||||
.com-dialog__content--fullscreen {
|
||||
:deep(.el-scrollbar__wrap) {
|
||||
height: calc(~'100vh - 46px - 60px'); // 最大高度
|
||||
}
|
||||
}
|
||||
.com-dialog__content--footer {
|
||||
:deep(.el-scrollbar__wrap) {
|
||||
max-height: calc(~'100vh - 46px - 60px - 70px'); // 最大高度
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,215 @@
|
|||
<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>
|
|
@ -0,0 +1,13 @@
|
|||
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 // 是否全屏
|
||||
}
|
|
@ -0,0 +1,267 @@
|
|||
<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 {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
.disabled__wrap {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
& > div {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-weight: bold;
|
||||
i {
|
||||
font-size: 30px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,9 @@
|
|||
export interface LogoTypes {
|
||||
src?: string
|
||||
logoSize?: number
|
||||
bgColor?: string
|
||||
borderSize?: number
|
||||
crossOrigin?: string
|
||||
borderRadius?: number
|
||||
logoRadius?: number
|
||||
}
|
|
@ -0,0 +1,337 @@
|
|||
<template>
|
||||
<div :class="{ search__col: layout === 'right' }">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="layout === 'right' ? 22 : 24">
|
||||
<el-form
|
||||
ref="ruleForm"
|
||||
inline
|
||||
:model="formInline"
|
||||
:rules="rules"
|
||||
:label-width="labelWidth"
|
||||
:label-position="labelPosition"
|
||||
:hide-required-asterisk="hideRequiredAsterisk"
|
||||
@submit.prevent
|
||||
>
|
||||
<el-form-item
|
||||
v-for="(item, $index) in data"
|
||||
:key="$index"
|
||||
:label="item.label"
|
||||
:prop="item.field"
|
||||
:rules="item.rules"
|
||||
>
|
||||
<template v-if="item.itemType === 'switch'">
|
||||
<el-switch
|
||||
v-model="formInline[item.field]"
|
||||
v-bind="{ ...getItemBindValue(item) }"
|
||||
@change="
|
||||
(val) => {
|
||||
changeVal(val, item)
|
||||
}
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-if="item.itemType === 'input'">
|
||||
<el-input
|
||||
v-model="formInline[item.field]"
|
||||
v-bind="{ ...getItemBindValue(item) }"
|
||||
@change="
|
||||
(val) => {
|
||||
changeVal(val, item)
|
||||
}
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-if="item.itemType === 'select'">
|
||||
<el-select
|
||||
v-model="formInline[item.field]"
|
||||
v-bind="{ ...getItemBindValue(item) }"
|
||||
@change="
|
||||
(val) => {
|
||||
changeVal(val, item)
|
||||
}
|
||||
"
|
||||
>
|
||||
<el-option
|
||||
v-for="v in item.options"
|
||||
:key="item.optionValue ? v[item.optionValue] : v.value"
|
||||
:value="item.optionValue ? v[item.optionValue] : v.value"
|
||||
:label="item.optionLabel ? v[item.optionLabel] : v.title"
|
||||
/>
|
||||
</el-select>
|
||||
</template>
|
||||
|
||||
<template v-if="item.itemType === 'radio'">
|
||||
<el-radio-group
|
||||
v-model="formInline[item.field]"
|
||||
@change="
|
||||
(val) => {
|
||||
changeVal(val, item)
|
||||
}
|
||||
"
|
||||
>
|
||||
<template v-if="item.radioType === 'radio'">
|
||||
<el-radio
|
||||
v-for="v in item.options"
|
||||
:key="item.optionValue ? v[item.optionValue] : v.value"
|
||||
v-bind="{ ...getItemBindValue(item) }"
|
||||
:label="item.optionValue ? v[item.optionValue] : v.value"
|
||||
>
|
||||
{{ item.optionLabel ? v[item.optionLabel] : v.label }}
|
||||
</el-radio>
|
||||
</template>
|
||||
<template v-else-if="item.radioType === 'button'">
|
||||
<el-radio-button
|
||||
v-for="v in item.options"
|
||||
:key="item.optionValue ? v[item.optionValue] : v.value"
|
||||
v-bind="{ ...getItemBindValue(item) }"
|
||||
:label="item.optionValue ? v[item.optionValue] : v.value"
|
||||
>
|
||||
{{ item.optionLabel ? v[item.optionLabel] : v.label }}
|
||||
</el-radio-button>
|
||||
</template>
|
||||
</el-radio-group>
|
||||
</template>
|
||||
|
||||
<!-- element近期会新增treeSelect组件,所以不打算在自己维护一套。等待ing -->
|
||||
<!-- <template v-if="item.itemType === 'treeSelect'">
|
||||
<el-tree-select
|
||||
v-model:value="formInline[item.field]"
|
||||
:size="item.size"
|
||||
:dropdown-style="item.dropdownStyle"
|
||||
:tree-data="item.options"
|
||||
:placeholder="item.placeholder"
|
||||
:tree-checkable="item.treeCheckable"
|
||||
:max-tag-count="item.maxTagCount"
|
||||
:tree-default-expand-all="item.treeDefaultExpandAll"
|
||||
:allow-clear="item.allowClear"
|
||||
style="min-width: 201px;"
|
||||
@change="((val) => {changeVal(val, item)})"
|
||||
>
|
||||
<template #title="{ title }">
|
||||
<span>{{ title }}</span>
|
||||
</template>
|
||||
</el-tree-select>
|
||||
</template> -->
|
||||
|
||||
<template v-if="item.itemType === 'timePicker'">
|
||||
<el-time-picker
|
||||
v-model="formInline[item.field]"
|
||||
v-bind="{ ...getItemBindValue(item) }"
|
||||
@change="
|
||||
(val) => {
|
||||
changeVal(val, item)
|
||||
}
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-if="item.itemType === 'timeSelect'">
|
||||
<el-time-select
|
||||
v-model="formInline[item.field]"
|
||||
v-bind="{ ...getItemBindValue(item) }"
|
||||
@change="
|
||||
(val) => {
|
||||
changeVal(val, item)
|
||||
}
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<template v-if="item.itemType === 'datePicker' || item.itemType === 'dateTimePicker'">
|
||||
<el-date-picker
|
||||
v-model="formInline[item.field]"
|
||||
v-bind="{ ...getItemBindValue(item) }"
|
||||
@change="
|
||||
(val) => {
|
||||
changeVal(val, item)
|
||||
}
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="data.length > 0 && layout === 'classic'">
|
||||
<el-button type="primary" icon="el-icon-search" @click="submitForm"> 查询 </el-button>
|
||||
<el-button v-if="showReset" icon="el-icon-refresh-right" @click="resetForm">
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
<el-col :span="layout === 'right' ? 2 : 24">
|
||||
<div
|
||||
v-if="data.length > 0 && (layout === 'bottom' || layout === 'right')"
|
||||
class="search__bottom"
|
||||
:class="{ 'search__bottom--col': layout === 'right' }"
|
||||
>
|
||||
<div class="search__bottom--button">
|
||||
<el-button type="primary" icon="el-icon-search" @click="submitForm"> 查询 </el-button>
|
||||
</div>
|
||||
<div class="search__bottom--button">
|
||||
<el-button
|
||||
v-if="showReset"
|
||||
:style="{
|
||||
'margin-left': layout !== 'right' ? '15px' : '0',
|
||||
'margin-top': layout === 'right' ? '27px' : '0'
|
||||
}"
|
||||
icon="el-icon-refresh-right"
|
||||
@click="resetForm"
|
||||
>
|
||||
重置
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts" name="Search">
|
||||
import { PropType, watch, ref, unref } from 'vue'
|
||||
import { deepClone } from '@/utils'
|
||||
|
||||
const props = defineProps({
|
||||
// 表单域标签的宽度,例如 '50px'。作为 Form 直接子元素的 form-item 会继承该值。支持 auto。
|
||||
labelWidth: {
|
||||
type: String as PropType<string>,
|
||||
default: ''
|
||||
},
|
||||
labelPosition: {
|
||||
type: String as PropType<'right' | 'left' | 'top'>,
|
||||
default: 'right'
|
||||
},
|
||||
// 隐藏所有表单项的必选标记
|
||||
hideRequiredAsterisk: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true
|
||||
},
|
||||
// 表单数据对象
|
||||
data: {
|
||||
type: Object as PropType<{ [key: string]: any }>,
|
||||
default: () => {}
|
||||
},
|
||||
// 表单验证规则
|
||||
rules: {
|
||||
type: Object as PropType<{ [key: number]: any }>,
|
||||
default: () => {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否显示重置按钮
|
||||
showReset: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: true
|
||||
},
|
||||
// 是否显示导出按钮
|
||||
showExport: {
|
||||
type: Boolean as PropType<boolean>,
|
||||
default: false
|
||||
},
|
||||
// 风格
|
||||
layout: {
|
||||
type: String as PropType<'classic' | 'bottom' | 'right'>,
|
||||
default: 'classic'
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['search-submit', 'reset-submit', 'change'])
|
||||
|
||||
const ruleForm = ref<HTMLElement | null>(null)
|
||||
const formInline = ref<IObj>({})
|
||||
watch(
|
||||
() => props.data,
|
||||
(data) => {
|
||||
initForm(data)
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
function getItemBindValue(item: any) {
|
||||
const delArr: string[] = ['label', 'itemType', 'value', 'field']
|
||||
const obj = deepClone(item)
|
||||
for (const key in obj) {
|
||||
if (delArr.indexOf(key) !== -1) {
|
||||
delete obj[key]
|
||||
}
|
||||
}
|
||||
return obj
|
||||
}
|
||||
|
||||
function initForm(data: any): void {
|
||||
for (const v of data) {
|
||||
formInline.value[v.field] = formInline.value[v.field] || v.value
|
||||
}
|
||||
}
|
||||
|
||||
async function submitForm(): Promise<void> {
|
||||
const form = unref(ruleForm) as any
|
||||
if (!form) return
|
||||
try {
|
||||
form.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
emit('search-submit', unref(formInline))
|
||||
} else {
|
||||
console.log('error submit!!')
|
||||
return false
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
|
||||
async function resetForm(): Promise<void> {
|
||||
const form = unref(ruleForm) as any
|
||||
if (!form) return
|
||||
await form.resetFields()
|
||||
emit('reset-submit', unref(formInline))
|
||||
}
|
||||
|
||||
function changeVal(val: any, item: any): void {
|
||||
if (item.onChange) {
|
||||
emit('change', {
|
||||
field: item.field,
|
||||
value: val
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ant-form-inline {
|
||||
.ant-form-item {
|
||||
min-height: 60px;
|
||||
}
|
||||
.ant-form-item-with-help {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.search__bottom {
|
||||
text-align: center;
|
||||
padding-bottom: 20px;
|
||||
.search__bottom--button {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
.search__bottom--col {
|
||||
padding-bottom: 0;
|
||||
margin-top: 5px;
|
||||
position: relative;
|
||||
.search__bottom--button {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
.search__bottom--col::before {
|
||||
content: '';
|
||||
width: 1px;
|
||||
height: 100%;
|
||||
border-left: 1px solid #d9d9d9;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
</style>
|
|
@ -1,6 +1,10 @@
|
|||
import type { App } from 'vue'
|
||||
import SvgIcon from './SvgIcon/index.vue' // svg组件
|
||||
import ComSearch from './Search/index.vue' // search组件
|
||||
import ComDialog from './Dialog/index.vue' // dialog组件
|
||||
|
||||
export function setupGlobCom(app: App<Element>): void {
|
||||
app.component('SvgIcon', SvgIcon)
|
||||
app.component('ComSearch', ComSearch)
|
||||
app.component('ComDialog', ComDialog)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import type { App } from 'vue'
|
||||
|
||||
import { clipboard } from './clipboard'
|
||||
|
||||
export function setupDirectives(app: App) {
|
||||
app.directive('clipboard', clipboard)
|
||||
}
|
|
@ -27,7 +27,7 @@ const tagsViewStore = useTagsViewStore()
|
|||
import { useCache } from '@/hooks/web/useCache'
|
||||
const { wsCache } = useCache()
|
||||
// @ts-ignore
|
||||
import avatarImg from '@/assets/img/avatar.gif'
|
||||
import avatarImg from '@/assets/img/avatar.png'
|
||||
|
||||
const { replace, push } = useRouter()
|
||||
async function loginOut(): Promise<void> {
|
||||
|
|
|
@ -6,8 +6,12 @@ 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'
|
||||
|
@ -23,6 +27,10 @@ setupStore(app) // 引入状态管理
|
|||
|
||||
setupRouter(app) // 引入路由
|
||||
|
||||
setupDirectives(app)
|
||||
|
||||
setupElement(app)
|
||||
|
||||
setupGlobCom(app) // 引入全局组件
|
||||
|
||||
router.isReady().then(() => {
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* 为了保持多页element组件的样式统一,提供全局配置的方法。
|
||||
*/
|
||||
import { ConfigElement } from './types'
|
||||
|
||||
const elementConfig: ConfigElement = {
|
||||
/**
|
||||
* 尺寸
|
||||
*/
|
||||
size: 'medium',
|
||||
|
||||
/**
|
||||
* 层级
|
||||
*/
|
||||
zIndex: 2000
|
||||
}
|
||||
|
||||
export default elementConfig
|
|
@ -0,0 +1,23 @@
|
|||
// 按需加载element
|
||||
// 目前需要手动引入loading等插件,无法自动导入
|
||||
// size和zIndex也需要这样设置,暂时还无法在全局配置组件中去设置
|
||||
// 需要看看后面官方是不是能优化这点
|
||||
import type { App } from 'vue'
|
||||
|
||||
import ElementConfig from './element.config'
|
||||
|
||||
// element全局配置项
|
||||
const { size, zIndex } = ElementConfig
|
||||
|
||||
import { ElLoading } from 'element-plus'
|
||||
|
||||
const plugins = [ElLoading]
|
||||
|
||||
export function setupElement(app: App<Element>): void {
|
||||
plugins.forEach((plugin: any) => {
|
||||
app.use(plugin)
|
||||
})
|
||||
|
||||
// 全局配置
|
||||
app.config.globalProperties.$ELEMENT = { size: size, zIndex: zIndex }
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
/**
|
||||
* element配置
|
||||
*/
|
||||
export interface ConfigElement {
|
||||
zIndex: number
|
||||
size: 'medium' | 'small' | 'mini'
|
||||
}
|
|
@ -144,71 +144,47 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
|
|||
meta: {
|
||||
title: '图片预览'
|
||||
}
|
||||
}
|
||||
// {
|
||||
// path: 'button',
|
||||
// component: () => import('_v/components-demo/button/index.vue'),
|
||||
// name: 'ButtonDemo',
|
||||
// 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: 'markdown',
|
||||
// component: () => import('_v/components-demo/markdown/index.vue'),
|
||||
// name: 'MarkdownDemo',
|
||||
// meta: {
|
||||
// title: 'markdown编辑器'
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'dialog',
|
||||
// component: () => import('_v/components-demo/dialog/index.vue'),
|
||||
// name: 'DialogDemo',
|
||||
// meta: {
|
||||
// title: '弹窗'
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'more',
|
||||
// component: () => import('_v/components-demo/more/index.vue'),
|
||||
// name: 'MoreDemo',
|
||||
// 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'),
|
||||
|
@ -217,14 +193,14 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
|
|||
// title: '详情组件'
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// path: 'qrcode',
|
||||
// component: () => import('_v/components-demo/qrcode/index.vue'),
|
||||
// name: 'QrcodeDemo',
|
||||
// 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'),
|
||||
|
|
|
@ -0,0 +1,111 @@
|
|||
<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="text-align: center; margin-top: 20px">
|
||||
<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 {
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
&__item {
|
||||
font-size: 80px;
|
||||
color: #f6416c;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
.action {
|
||||
margin-top: 20px;
|
||||
&__item {
|
||||
padding: 0 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
& > span {
|
||||
display: inline-block;
|
||||
width: 120px;
|
||||
text-align: center;
|
||||
}
|
||||
:deep(.el-input-number) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,27 @@
|
|||
<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>
|
|
@ -0,0 +1,45 @@
|
|||
<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>
|
|
@ -0,0 +1,24 @@
|
|||
<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>
|
|
@ -0,0 +1,89 @@
|
|||
<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 {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
.title-item {
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,149 @@
|
|||
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: '请选择日期'
|
||||
}
|
||||
]
|
|
@ -0,0 +1,95 @@
|
|||
<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-bottom: 20px; margin-top: 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-bottom: 20px; margin-top: 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-bottom: 20px; margin-top: 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 {
|
||||
background: #fff;
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
Loading…
Reference in New Issue