依赖设计与控件优化

This commit is contained in:
gary.fu
2025-06-19 18:18:48 +08:00
parent 4a58f10027
commit d69f66d4a8
18 changed files with 1137 additions and 862 deletions

1759
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,44 +9,48 @@
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore",
"hmr": "vite --debug hmr"
},
"repository": {
"type": "git",
"url": "https://github.com/fugary/simple-element-plus-template"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@guolao/vue-monaco-editor": "^1.5.1",
"@guolao/vue-monaco-editor": "^1.5.5",
"@howiefh/ant-path-matcher": "^0.0.4",
"@vicons/material": "^0.12.0",
"@vueuse/core": "^10.11.0",
"@vueuse/core": "^10.11.1",
"async-validator": "^4.2.5",
"axios": "^1.7.2",
"dayjs": "^1.11.11",
"axios": "^1.7.9",
"dayjs": "^1.11.13",
"echarts": "^5.5.0",
"element-plus": "^2.7.5",
"element-plus": "^2.9.11",
"lodash-es": "^4.17.21",
"monaco-editor": "^0.47.0",
"monaco-editor": "^0.52.2",
"nprogress": "^0.2.0",
"numeral": "^2.0.6",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"pinia": "^2.3.1",
"pinia-plugin-persistedstate": "^3.2.3",
"split.js": "^1.6.5",
"ua-parser-js": "^1.0.38",
"vite-plugin-mock": "^3.0.1",
"vue": "^3.4.29",
"vue": "^3.4.38",
"vue-echarts": "^6.7.3",
"vue-i18n": "^9.13.1",
"vue-router": "^4.3.3",
"vue-i18n": "^9.14.4",
"vue-router": "^4.5.1",
"vue-virtual-scroller": "^2.0.0-beta.8"
},
"devDependencies": {
"@rollup/rollup-win32-x64-msvc": "^4.18.0",
"@rushstack/eslint-patch": "^1.10.3",
"@rushstack/eslint-patch": "^1.10.5",
"@typescript-eslint/eslint-plugin": "^7.13.1",
"@typescript-eslint/parser": "^7.13.1",
"@vitejs/plugin-vue": "^5.0.5",
"@typescript-eslint/parser": "^7.18.0",
"@vitejs/plugin-vue": "^5.2.4",
"@vitejs/plugin-vue-jsx": "^3.1.0",
"@vue/eslint-config-standard": "^8.0.1",
"eslint": "^8.57.0",
"eslint-plugin-vue": "^9.26.0",
"rollup-plugin-visualizer": "^5.12.0",
"typescript": "^5.4.5",
"vite": "^5.3.1",
"eslint": "^8.57.1",
"eslint-plugin-vue": "^9.32.0",
"rollup-plugin-visualizer": "^5.14.0",
"typescript": "^5.7.3",
"vite": "^5.4.19",
"vite-plugin-eslint": "^1.8.1"
}
}

View File

@@ -20,16 +20,18 @@ const props = defineProps({
const calcItems = computed(() => {
return props.items.filter(item => item.enabled !== false).map(item => {
const label = item.labelKey ? toLabelByKey(item.labelKey) : item.label
let label = item.labelKey ? toLabelByKey(item.labelKey) : item.label
label = isFunction(item.labelFormatter) ? item.labelFormatter(item) : label
const value = isFunction(item.formatter) ? item.formatter(item) : item.value
const labelResult = { label, vnode: isVNode(label) }
const valueResult = { value, vnode: isVNode(value) }
const slotsResult = calcSlotsResult(item)
return {
...item,
label,
width: item.width || props.width,
minWidth: item.minWidth || props.minWidth,
valueResult,
labelResult,
slotsResult
}
})
@@ -44,7 +46,6 @@ const calcItems = computed(() => {
<el-descriptions-item
v-for="(calcItem, index) in calcItems"
:key="index"
:label="calcItem.label"
:min-width="calcItem.minWidth"
:width="calcItem.width"
:span="calcItem.span"
@@ -64,6 +65,19 @@ const calcItems = computed(() => {
{{ calcItem.slotsResult[slotKey].result }}
</template>
</template>
<template
v-if="calcItem.labelResult.label"
#label
>
<span
v-if="calcItem.labelResult.label&&!calcItem.labelResult.vnode"
v-html="calcItem.labelResult.label"
/>
<component
:is="calcItem.labelResult.label"
v-if="calcItem.labelResult.vnode"
/>
</template>
<template v-if="calcItem.valueResult.value">
<span
v-if="calcItem.valueResult.value&&!calcItem.valueResult.vnode"

View File

@@ -124,7 +124,7 @@ const slots = computed(() => {
<el-link
class="margin-left1 margin-top1"
type="primary"
:underline="false"
underline="never"
@click="setUnlimited()"
>
<el-tag

View File

@@ -57,7 +57,7 @@ const tooltipFunc = ($event) => {
>
<span>
<el-link
:underline="false"
underline="never"
@click="tooltipFunc($event)"
>&nbsp;
<common-icon

View File

@@ -258,14 +258,16 @@ const formatResult = computed(() => {
:content="calcOption.tooltip"
placement="top-start"
raw-content
v-bind="calcOption.tooltipAttrs"
>
<span>
<el-link
:underline="false"
v-bind="calcOption.tooltipLinkAttrs"
underline="never"
@click="calcOption.tooltipFunc"
>&nbsp;
<common-icon
icon="QuestionFilled"
:icon="calcOption.tooltipIcon||'QuestionFilled'"
/>
</el-link>
</span>
@@ -288,7 +290,7 @@ const formatResult = computed(() => {
>
<component
:is="scope[`__slotResult__${slotKey}`]"
v-if="isVNode(scope[`__slotResult__${slotKey}`] = slot(scope))"
v-if="isVNode(scope[`__slotResult__${slotKey}`] = slot(scope, calcOption))"
/>
<template v-else>
{{ scope[`__slotResult__${slotKey}`] }}

View File

@@ -4,6 +4,7 @@ import {
InputProps, InputNumberProps, CascaderProps,
RadioGroupProps, RadioProps, RadioButtonProps,
CheckboxProps, CheckboxGroupProps, CheckboxButtonProps,
LinkProps, ElTooltipProps,
DatePickerProps, timePickerDefaultProps, SwitchProps, SliderProps, TransferProps
} from 'element-plus'
import { SelectProps as SelectV1Props } from 'element-plus/es/components/select/src/select'
@@ -137,6 +138,12 @@ export interface CommonFormOption extends FormControlTypeOption {
change?: (val: any) => void;
/** 提示信息 */
tooltip?: string;
/** 图标 */
tooltipIcon?: string;
/** tooltip配置 */
tooltipAttrs?: ElTooltipProps;
/** tooltip link配置 */
tooltipLinkAttrs?: LinkProps;
/** 提示函数 */
tooltipFunc?: () => void;
/** 自动trim默认false**/

View File

@@ -15,6 +15,14 @@ const calcIcon = computed(() => {
}
return props.icon
})
const customIcon = computed(() => {
if (props.icon?.startsWith('custom')) {
return props.icon
}
return ''
})
</script>
<template>
@@ -24,6 +32,12 @@ const calcIcon = computed(() => {
>
<component
:is="calcIcon"
v-if="!customIcon"
/>
<span
v-else
class="custom-icon"
:class="customIcon"
/>
</el-icon>
</template>

View File

@@ -0,0 +1,68 @@
<script setup>
import { onMounted, ref, useAttrs } from 'vue'
import Split from 'split.js'
/**
* 更多属性配置可以参考文档
* @link https://github.com/nathancahill/split/tree/master/packages/splitjs <br>
*/
const props = defineProps({
sizes: {
type: Array,
default: () => [25, 75]
},
minSize: {
type: [Number, Array],
default: 100
},
maxSize: {
type: [Number, Array],
default: Infinity
},
direction: {
type: String,
default: 'horizontal',
validator (value) {
return ['horizontal', 'vertical'].includes(value)
}
},
gutterAlign: {
type: String,
default: 'center',
validator (value) {
return ['start', 'center', 'end'].includes(value)
}
}
})
const itemRefs = ref([])
const attrs = useAttrs()
onMounted(() => {
Split(itemRefs.value.map(itemRef => itemRef), {
sizes: props.sizes,
minSize: props.minSize,
maxSize: props.maxSize,
gutterAlign: props.gutterAlign,
direction: props.direction,
...attrs
})
})
</script>
<template>
<div class="common-split">
<div
v-for="(_, index) in sizes"
ref="itemRefs"
:key="index"
>
<slot :name="`split-${index}`" />
</div>
</div>
</template>
<style scoped>
</style>

View File

@@ -119,7 +119,7 @@ const options = computed(() => {
circle
type="danger"
size="small"
:underline="false"
underline="never"
@click="deleteItem(row, $index)"
>
<common-icon

View File

@@ -1,7 +1,7 @@
<script setup>
import { formatDate } from '@/utils'
import { computed, useSlots } from 'vue'
import { get } from 'lodash-es'
import { computed, isVNode, useSlots } from 'vue'
import { get, isFunction } from 'lodash-es'
import { toLabelByKey } from '@/components/utils'
import TableDynamicButton from '@/components/common-table/table-dynamic-button.vue'
@@ -17,6 +17,10 @@ const props = defineProps({
type: Object,
required: true
},
columnIndex: {
type: Number,
default: 0
},
/**
* @type {''|'large'|'small'|'default'}
*/
@@ -43,18 +47,45 @@ const getPropertyData = (row) => {
const slots = useSlots()
const columnLabel = computed(() => {
return props.column.label || toLabelByKey(props.column.labelKey)
})
const headerResult = computed(() => {
const column = props.column
if (isFunction(column.headerFormatter)) {
const header = column.headerFormatter(column)
return { header, vnode: isVNode(header) }
}
return null
})
</script>
<template>
<el-table-column
v-if="!column.isOperation"
:label="column.label || toLabelByKey(column.labelKey)"
:key="`${columnLabel}-${columnIndex}`"
:label="columnLabel"
:prop="column.prop||column.property"
:width="column.width"
:min-width="column.minWidth"
v-bind="column.attrs"
:formatter="formatter"
>
<template
v-if="headerResult"
#header
>
<span
v-if="headerResult.header&&!headerResult.vnode"
v-html="headerResult.header"
/>
<component
:is="headerResult.header"
v-if="headerResult.vnode"
/>
</template>
<template
v-if="column.click"
#default="scope"

View File

@@ -267,6 +267,7 @@ defineExpose({
<common-table-column
v-for="(column, index) in calcColumns"
:key="index"
:column-index="index"
:column="column"
:button-size="buttonSize"
>

View File

@@ -1,7 +1,7 @@
import { ButtonProps, LinkProps, TableProps, PaginationProps } from 'element-plus'
import tableColumnProps from 'element-plus/es/components/table/src/table-column/defaults'
import { CommonPage } from '../public'
import { ExtractPropTypes } from 'vue'
import { ExtractPropTypes, VNode } from 'vue'
export type TableColumnProps = ExtractPropTypes<typeof tableColumnProps>
@@ -52,7 +52,9 @@ export interface CommonTableColumn {
/** 点击事件 */
click?: (data: any) => any;
/** 格式化函数 */
formatter?: (data: any, scope: any) => string;
formatter?: (data: any, scope: any) => string|VNode;
/** header格式化 */
headerFormatter?: (data: any, scope: any) => string|VNode;
/** 日期格式化 */
dateFormat?: string
}

View File

@@ -38,6 +38,7 @@ const button = computed(() => {
<template>
<el-button
v-if="button.enabled!==false"
v-common-tooltip="button.tooltip"
:type="button.type"
:size="button.size||buttonSize"
:disabled="button.disabled"

View File

@@ -46,6 +46,7 @@ const getTooltipDirective = (props) => {
el.tooltipConfig = calcTooltipConfig(binding)
},
unmounted (el) {
el.tooltipVnode?.component?.exposed?.showOrHideTooltip(false)
el.tooltipDynamicHelper?.destroy()
}
}

View File

@@ -16,6 +16,7 @@ import CommonWindow from '@/components/common-window/index.vue'
import CommonAutocomplete from '@/components/common-autocomplete/index.vue'
import CommonSort from '@/components/common-sort/index.vue'
import CommonDescriptions from '@/components/common-descriptions/index.vue'
import CommonSplit from '@/components/common-split/index.vue'
import CommonDirectives from '@/components/directives'
/**
@@ -44,6 +45,7 @@ export default {
Vue.component('CommonAutocomplete', CommonAutocomplete)
Vue.component('CommonSort', CommonSort)
Vue.component('CommonDescriptions', CommonDescriptions)
Vue.component('CommonSplit', CommonSplit)
Vue.use(CommonDirectives)
}
}

View File

@@ -154,6 +154,13 @@ export const proxyMethod = (targets = [], methodName) => {
}
}
export const limitStr = (str, len) => {
if (str && str.length > len) {
return str.substring(0, len) + '...'
}
return str
}
export const calcSlotsResult = (config, data) => {
const results = {}
if (config?.slots) {

View File

@@ -68,7 +68,10 @@ export const useTabsViewStore = defineStore('tabsView', () => {
}
const findHistoryTab = (path) => {
const idx = historyTabs.value.findIndex(v => v.path === path)
let idx = historyTabs.value.findIndex(v => v.path === path)
if (idx === -1) {
idx = historyTabs.value.findIndex(v => v.fullPath === path)
}
if (idx > -1) {
return historyTabs.value[idx]
}
@@ -99,15 +102,22 @@ export const useTabsViewStore = defineStore('tabsView', () => {
// 可能是Proxy需要解析出来
isHomeTab(tab) ? historyTabs.value.unshift({ ...tab }) : historyTabs.value.push({ ...tab })
}
tab.accessTime = Date.now()
if (isNestedRoute(tab)) {
addNestedParentTab(tab, replaceTab)
} else {
addCachedTab(tab, replaceTab)
}
} else {
historyTabs.value[idx].accessTime = Date.now()
}
}
}
const findLastTab = () => {
return historyTabs.value.toSorted((a, b) => b.accessTime - a.accessTime)?.[0]
}
const removeHistoryTab = tab => {
if (historyTabs.value.length > 1) {
const idx = historyTabs.value.findIndex(v => v.path === tab.path)
@@ -116,7 +126,7 @@ export const useTabsViewStore = defineStore('tabsView', () => {
// 删除tab
historyTabs.value.splice(idx, 1)
}
return historyTabs.value[historyTabs.value.length - 1]
return findLastTab()
}
}
@@ -227,5 +237,7 @@ export const useTabsViewStore = defineStore('tabsView', () => {
hasCloseDropdown
}
}, {
persist: { serializer }
persist: {
serializer
}
})