格式化文档

This commit is contained in:
2026-01-10 10:10:57 +08:00
parent 1cc427cbb0
commit a0afedf5f3
224 changed files with 27320 additions and 26980 deletions
+2 -2
View File
@@ -1,7 +1,7 @@
{
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"tabWidth": 4,
"useTabs": true,
"semi": false,
"vueIndentScriptAndStyle": true,
"singleQuote": true,
+3 -13
View File
@@ -1,10 +1,9 @@
{
"name": "art-design-pro",
"version": "0.0.0",
"version": "0.1.0",
"type": "module",
"engines": {
"node": ">=20.19.0",
"pnpm": ">=8.8.0"
"node": ">=20.19.0"
},
"scripts": {
"dev": "vite --open",
@@ -13,16 +12,7 @@
"lint": "eslint",
"fix": "eslint --fix",
"lint:prettier": "prettier --write \"**/*.{js,cjs,ts,json,tsx,css,less,scss,vue,html,md}\"",
"lint:stylelint": "stylelint \"**/*.{css,scss,vue}\" --fix",
"lint:lint-staged": "lint-staged",
"prepare": "husky",
"commit": "git-cz",
"clean:dev": "tsx scripts/clean-dev.ts"
},
"config": {
"commitizen": {
"path": "node_modules/cz-git"
}
"lint:stylelint": "stylelint \"**/*.{css,scss,vue}\" --fix"
},
"lint-staged": {
"*.{js,ts,mjs,mts,tsx}": [
+4 -3
View File
@@ -151,7 +151,8 @@
var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
var(--tw-backdrop-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
var(--tw-backdrop-sepia);
}
@@ -24,14 +24,19 @@
<div class="basic-banner__content">
<!-- title slot -->
<slot name="title">
<p v-if="title" class="basic-banner__title" :style="{ color: titleColor }">{{ title }}</p>
<p v-if="title" class="basic-banner__title" :style="{ color: titleColor }">{{
title
}}</p>
</slot>
<!-- subtitle slot -->
<slot name="subtitle">
<p v-if="subtitle" class="basic-banner__subtitle" :style="{ color: subtitleColor }">{{
subtitle
}}</p>
<p
v-if="subtitle"
class="basic-banner__subtitle"
:style="{ color: subtitleColor }"
>{{ subtitle }}</p
>
</slot>
<!-- button slot -->
@@ -58,7 +63,11 @@
v-if="imageConfig.src"
class="basic-banner__background-image"
:src="imageConfig.src"
:style="{ width: imageConfig.width, bottom: imageConfig.bottom, right: imageConfig.right }"
:style="{
width: imageConfig.width,
bottom: imageConfig.bottom,
right: imageConfig.right
}"
loading="lazy"
alt="背景图片"
/>
@@ -10,7 +10,10 @@
</div>
<div
class="text-sm font-medium text-danger"
:class="[percentage > 0 ? 'text-success' : '', isMiniChart ? 'absolute bottom-5' : '']"
:class="[
percentage > 0 ? 'text-success' : '',
isMiniChart ? 'absolute bottom-5' : ''
]"
>
{{ percentage > 0 ? '+' : '' }}{{ percentage }}%
</div>
@@ -21,7 +24,9 @@
<div
ref="chartRef"
class="absolute bottom-0 left-0 right-0 mx-auto"
:class="isMiniChart ? '!absolute !top-5 !right-5 !bottom-auto !left-auto !h-15 !w-4/10' : ''"
:class="
isMiniChart ? '!absolute !top-5 !right-5 !bottom-auto !left-auto !h-15 !w-4/10' : ''
"
:style="{ height: isMiniChart ? '60px' : `calc(${height}rem - 5rem)` }"
></div>
</div>
@@ -24,7 +24,9 @@
<div
ref="chartRef"
class="absolute bottom-0 left-0 right-0 box-border w-full"
:class="isMiniChart ? '!absolute !top-5 !right-5 !bottom-auto !left-auto !h-15 !w-4/10' : ''"
:class="
isMiniChart ? '!absolute !top-5 !right-5 !bottom-auto !left-auto !h-15 !w-4/10' : ''
"
:style="{ height: isMiniChart ? '60px' : `calc(${height}rem - 5rem)` }"
></div>
</div>
@@ -107,13 +109,15 @@
offset: 0,
color: props.color
? hexToRgba(props.color, 0.2).rgba
: hexToRgba(getCssVar('--el-color-primary'), 0.2).rgba
: hexToRgba(getCssVar('--el-color-primary'), 0.2)
.rgba
},
{
offset: 1,
color: props.color
? hexToRgba(props.color, 0.01).rgba
: hexToRgba(getCssVar('--el-color-primary'), 0.01).rgba
: hexToRgba(getCssVar('--el-color-primary'), 0.01)
.rgba
}
])
}
@@ -1,7 +1,10 @@
<!-- 进度条卡片 -->
<template>
<div class="art-card h-32 flex flex-col justify-center px-5">
<div class="mb-3.5 flex-c" :style="{ justifyContent: icon ? 'space-between' : 'flex-start' }">
<div
class="mb-3.5 flex-c"
:style="{ justifyContent: icon ? 'space-between' : 'flex-start' }"
>
<div v-if="icon" class="size-11 flex-cc bg-g-300 text-xl rounded-lg" :class="iconStyle">
<ArtSvgIcon :icon="icon" class="text-2xl"></ArtSvgIcon>
</div>
@@ -4,7 +4,11 @@
class="art-card h-32 flex-c px-5 transition-transform duration-200 hover:-translate-y-0.5"
:class="boxStyle"
>
<div v-if="icon" class="mr-4 size-11 flex-cc rounded-lg text-xl text-white" :class="iconStyle">
<div
v-if="icon"
class="mr-4 size-11 flex-cc rounded-lg text-xl text-white"
:class="iconStyle"
>
<ArtSvgIcon :icon="icon"></ArtSvgIcon>
</div>
<div class="flex-1">
@@ -18,7 +18,9 @@
<div class="flex-c gap-3">
<div class="flex-c gap-2">
<span class="text-sm">{{ item.content }}</span>
<span v-if="item.code" class="text-sm text-theme"> #{{ item.code }} </span>
<span v-if="item.code" class="text-sm text-theme">
#{{ item.code }}
</span>
</div>
</div>
</ElTimelineItem>
@@ -135,7 +135,9 @@
const multiData = props.data as BarDataItem[]
return (
!multiData.length ||
multiData.every((item) => !item.data?.length || item.data.every((val) => val === 0))
multiData.every(
(item) => !item.data?.length || item.data.every((val) => val === 0)
)
)
}
@@ -144,11 +146,15 @@
watchSources: [() => props.data, () => props.xAxisData, () => props.colors],
generateOptions: (): EChartsOption => {
const options: EChartsOption = {
grid: getGridWithLegend(props.showLegend && isMultipleData.value, props.legendPosition, {
grid: getGridWithLegend(
props.showLegend && isMultipleData.value,
props.legendPosition,
{
top: 15,
right: 0,
left: 0
}),
}
),
tooltip: props.showTooltip ? getTooltipStyle() : undefined,
xAxis: {
type: 'category',
@@ -139,7 +139,9 @@
const multiData = props.data as BarDataItem[]
return (
!multiData.length ||
multiData.every((item) => !item.data?.length || item.data.every((val) => val === 0))
multiData.every(
(item) => !item.data?.length || item.data.every((val) => val === 0)
)
)
}
@@ -148,11 +150,15 @@
watchSources: [() => props.data, () => props.xAxisData, () => props.colors],
generateOptions: (): EChartsOption => {
const options: EChartsOption = {
grid: getGridWithLegend(props.showLegend && isMultipleData.value, props.legendPosition, {
grid: getGridWithLegend(
props.showLegend && isMultipleData.value,
props.legendPosition,
{
top: 15,
right: 0,
left: 0
}),
}
),
tooltip: props.showTooltip ? getTooltipStyle() : undefined,
xAxis: {
type: 'value',
@@ -55,7 +55,8 @@
return (
!props.data?.length ||
props.data.every(
(item) => item.open === 0 && item.close === 0 && item.high === 0 && item.low === 0
(item) =>
item.open === 0 && item.close === 0 && item.high === 0 && item.low === 0
)
)
},
@@ -112,7 +113,12 @@
series: [
{
type: 'candlestick',
data: props.data.map((item) => [item.open, item.close, item.low, item.high]),
data: props.data.map((item) => [
item.open,
item.close,
item.low,
item.high
]),
itemStyle: {
color: upColor,
color0: downColor,
@@ -193,11 +193,15 @@
animation: true,
animationDuration: isInitial ? 0 : 1300,
animationDurationUpdate: isInitial ? 0 : 1300,
grid: getGridWithLegend(props.showLegend && isMultipleData.value, props.legendPosition, {
grid: getGridWithLegend(
props.showLegend && isMultipleData.value,
props.legendPosition,
{
top: 15,
right: 15,
left: 0
}),
}
),
tooltip: props.showTooltip ? getTooltipStyle() : undefined,
xAxis: {
type: 'category',
@@ -358,7 +362,9 @@
}
// 使用 VueUse 的 watchDebounced 优化数据监听(避免频繁更新)
watch([() => props.data, () => props.xAxisData, () => props.colors], renderChart, { deep: true })
watch([() => props.data, () => props.xAxisData, () => props.colors], renderChart, {
deep: true
})
// 生命周期
onMounted(() => {
@@ -36,7 +36,10 @@
const { chartRef, isDark, getAnimationConfig, getTooltipStyle } = useChartComponent({
props,
checkEmpty: () => {
return !props.data?.length || props.data.every((item) => item.value.every((val) => val === 0))
return (
!props.data?.length ||
props.data.every((item) => item.value.every((val) => val === 0))
)
},
watchSources: [() => props.data, () => props.indicator, () => props.colors],
generateOptions: (): EChartsOption => {
@@ -52,7 +52,10 @@
} = useChartComponent({
props,
checkEmpty: () => {
return !props.data?.length || props.data.every((item) => item.value.every((val) => val === 0))
return (
!props.data?.length ||
props.data.every((item) => item.value.every((val) => val === 0))
)
},
watchSources: [() => props.data, () => props.colors, () => props.symbolSize],
generateOptions: (): EChartsOption => {
@@ -96,13 +99,17 @@
itemStyle: {
color: computedColor,
shadowBlur: 6,
shadowColor: isDark.value ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)',
shadowColor: isDark.value
? 'rgba(255, 255, 255, 0.1)'
: 'rgba(0, 0, 0, 0.1)',
shadowOffsetY: 2
},
emphasis: {
itemStyle: {
shadowBlur: 12,
shadowColor: isDark.value ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'
shadowColor: isDark.value
? 'rgba(255, 255, 255, 0.2)'
: 'rgba(0, 0, 0, 0.2)'
},
scale: true
},
@@ -2,7 +2,10 @@
<template>
<div>
<ElDropdown v-if="hasAnyAuthItem">
<ArtIconButton icon="ri:more-2-fill" class="!size-8 bg-g-200 dark:bg-g-300/45 text-sm" />
<ArtIconButton
icon="ri:more-2-fill"
class="!size-8 bg-g-200 dark:bg-g-300/45 text-sm"
/>
<template #dropdown>
<ElDropdownMenu>
<template v-for="item in list" :key="item.key">
@@ -238,7 +238,8 @@
handler.value.style.transition = 'none'
// 计算拖拽起始位置
state.x =
(e.pageX || e.touches[0].pageX) - parseInt(handler.value.style.left.replace('px', ''), 10)
(e.pageX || e.touches[0].pageX) -
parseInt(handler.value.style.left.replace('px', ''), 10)
}
emit('handlerMove')
}
@@ -218,7 +218,9 @@
return columns.map((column) => {
// 使用配置的列宽度
const configWidth = Object.values(props.columns).find((col) => col.title === column)?.width
const configWidth = Object.values(props.columns).find(
(col) => col.title === column
)?.width
if (configWidth) {
return { wch: configWidth }
@@ -310,7 +312,11 @@
return Promise.resolve()
} catch (error) {
throw new ExportError(`Excel 导出失败: ${(error as Error).message}`, 'EXPORT_FAILED', error)
throw new ExportError(
`Excel 导出失败: ${(error as Error).message}`,
'EXPORT_FAILED',
error
)
}
}
@@ -344,7 +350,11 @@
const exportError =
error instanceof ExportError
? error
: new ExportError(`导出失败: ${(error as Error).message}`, 'UNKNOWN_ERROR', error)
: new ExportError(
`导出失败: ${(error as Error).message}`,
'UNKNOWN_ERROR',
error
)
// 触发错误事件
emit('export-error', exportError)
+17 -4
View File
@@ -43,7 +43,9 @@
</template>
<!-- 复选框组 -->
<template v-if="item.type === 'checkboxgroup' && getProps(item)?.options">
<template
v-if="item.type === 'checkboxgroup' && getProps(item)?.options"
>
<ElCheckbox
v-for="option in getProps(item).options"
v-bind="option"
@@ -52,7 +54,9 @@
</template>
<!-- 单选框组 -->
<template v-if="item.type === 'radiogroup' && getProps(item)?.options">
<template
v-if="item.type === 'radiogroup' && getProps(item)?.options"
>
<ElRadio
v-for="option in getProps(item).options"
v-bind="option"
@@ -61,7 +65,11 @@
</template>
<!-- 动态插槽支持 -->
<template v-for="(slotFn, slotName) in getSlots(item)" :key="slotName" #[slotName]>
<template
v-for="(slotFn, slotName) in getSlots(item)"
:key="slotName"
#[slotName]
>
<component :is="slotFn" />
</template>
</component>
@@ -74,7 +82,12 @@
:style="actionButtonsStyle"
>
<div class="flex gap-2 md:justify-center">
<ElButton v-if="showReset" class="reset-button" @click="handleReset" v-ripple>
<ElButton
v-if="showReset"
class="reset-button"
@click="handleReset"
v-ripple
>
{{ t('table.form.reset') }}
</ElButton>
<ElButton
@@ -43,7 +43,9 @@
</template>
<!-- 复选框组 -->
<template v-if="item.type === 'checkboxgroup' && getProps(item)?.options">
<template
v-if="item.type === 'checkboxgroup' && getProps(item)?.options"
>
<ElCheckbox
v-for="option in getProps(item).options"
v-bind="option"
@@ -52,7 +54,9 @@
</template>
<!-- 单选框组 -->
<template v-if="item.type === 'radiogroup' && getProps(item)?.options">
<template
v-if="item.type === 'radiogroup' && getProps(item)?.options"
>
<ElRadio
v-for="option in getProps(item).options"
v-bind="option"
@@ -61,7 +65,11 @@
</template>
<!-- 动态插槽支持 -->
<template v-for="(slotFn, slotName) in getSlots(item)" :key="slotName" #[slotName]>
<template
v-for="(slotFn, slotName) in getSlots(item)"
:key="slotName"
#[slotName]
>
<component :is="slotFn" />
</template>
</component>
@@ -71,7 +79,12 @@
<ElCol :xs="24" :sm="24" :md="span" :lg="span" :xl="span" class="action-column">
<div class="action-buttons-wrapper" :style="actionButtonsStyle">
<div class="form-buttons">
<ElButton v-if="showReset" class="reset-button" @click="handleReset" v-ripple>
<ElButton
v-if="showReset"
class="reset-button"
@click="handleReset"
v-ripple
>
{{ t('table.searchBar.reset') }}
</ElButton>
<ElButton
@@ -85,7 +98,11 @@
{{ t('table.searchBar.search') }}
</ElButton>
</div>
<div v-if="shouldShowExpandToggle" class="filter-toggle" @click="toggleExpand">
<div
v-if="shouldShowExpandToggle"
class="filter-toggle"
@click="toggleExpand"
>
<span>{{ expandToggleText }}</span>
<div class="icon-wrapper">
<ElIcon>
@@ -298,7 +315,9 @@
const shouldShowExpandToggle = computed(() => {
const filteredItems = props.items.filter((item) => !item.hidden)
return (
!props.isExpand && props.showExpand && filteredItems.length > Math.floor(24 / props.span) - 1
!props.isExpand &&
props.showExpand &&
filteredItems.length > Math.floor(24 / props.span) - 1
)
})
@@ -73,7 +73,8 @@
// 计算属性:上传服务器地址
const uploadServer = computed(
() =>
props.uploadConfig?.server || `${import.meta.env.VITE_API_URL}/api/common/upload/wangeditor`
props.uploadConfig?.server ||
`${import.meta.env.VITE_API_URL}/api/common/upload/wangeditor`
)
// 合并上传配置
@@ -168,7 +169,9 @@
}
const toolbar = editorContainer.querySelector('.w-e-toolbar')
const toolbarButtons = editorContainer.querySelectorAll('.w-e-bar-item button[data-menu-key]')
const toolbarButtons = editorContainer.querySelectorAll(
'.w-e-bar-item button[data-menu-key]'
)
if (toolbar && toolbarButtons.length > 0) {
return
@@ -1,7 +1,11 @@
<!-- 系统聊天窗口 -->
<template>
<div>
<ElDrawer v-model="isDrawerVisible" :size="isMobile ? '100%' : '480px'" :with-header="false">
<ElDrawer
v-model="isDrawerVisible"
:size="isMobile ? '100%' : '480px'"
:with-header="false"
>
<div class="mb-5 flex-cb">
<div>
<span class="text-base font-medium">Art Bot</span>
@@ -34,7 +38,10 @@
>
<ElAvatar :size="32" :src="message.avatar" class="shrink-0" />
<div
:class="['flex max-w-[70%] flex-col', message.isMe ? 'items-end' : 'items-start']"
:class="[
'flex max-w-[70%] flex-col',
message.isMe ? 'items-end' : 'items-start'
]"
>
<div
:class="[
@@ -48,7 +55,9 @@
<div
:class="[
'rounded-md px-3.5 py-2.5 text-sm leading-[1.4] text-g-900',
message.isMe ? 'message-right bg-theme/15' : 'message-left bg-g-300/50'
message.isMe
? 'message-right bg-theme/15'
: 'message-left bg-g-300/50'
]"
>{{ message.content }}</div
>
@@ -71,16 +80,23 @@
<div class="flex gap-2 py-2">
<ElButton :icon="Paperclip" circle plain />
<ElButton :icon="Picture" circle plain />
<ElButton type="primary" @click="sendMessage" v-ripple>发送</ElButton>
<ElButton type="primary" @click="sendMessage" v-ripple
>发送</ElButton
>
</div>
</template>
</ElInput>
<div class="mt-3 flex-cb">
<div class="flex-c">
<ArtSvgIcon icon="ri:image-line" class="mr-5 c-p text-g-600 text-lg" />
<ArtSvgIcon icon="ri:emotion-happy-line" class="mr-5 c-p text-g-600 text-lg" />
<ArtSvgIcon
icon="ri:emotion-happy-line"
class="mr-5 c-p text-g-600 text-lg"
/>
</div>
<ElButton type="primary" @click="sendMessage" v-ripple class="min-w-20">发送</ElButton>
<ElButton type="primary" @click="sendMessage" v-ripple class="min-w-20"
>发送</ElButton
>
</div>
</div>
</div>
@@ -146,7 +162,8 @@
{
id: 3,
sender: BOT_NAME,
content: '好的,我来为您介绍系统的主要功能。首先,您可以通过左侧菜单访问不同的功能模块...',
content:
'好的,我来为您介绍系统的主要功能。首先,您可以通过左侧菜单访问不同的功能模块...',
time: '10:02',
isMe: false,
avatar: aiAvatar
@@ -29,7 +29,9 @@
class="mr-3 c-p flex-c gap-3 rounded-lg p-2 hover:bg-g-200/70 dark:hover:bg-g-200/90 hover:[&_.app-icon]:!bg-transparent"
@click="handleApplicationClick(application)"
>
<div class="app-icon size-12 flex-cc rounded-lg bg-g-200/80 dark:bg-g-300/30">
<div
class="app-icon size-12 flex-cc rounded-lg bg-g-200/80 dark:bg-g-300/30"
>
<ArtSvgIcon
class="text-xl"
:icon="application.icon"
@@ -37,7 +39,9 @@
/>
</div>
<div>
<h3 class="m-0 text-sm font-medium text-g-800">{{ application.name }}</h3>
<h3 class="m-0 text-sm font-medium text-g-800">{{
application.name
}}</h3>
<p class="mt-1 text-xs text-g-600">{{ application.description }}</p>
</div>
</div>
@@ -292,7 +292,8 @@
const startY = this.canvasHeight
// 根据是否有图片确定可用形状
const availableShapes = imageUrl && this.imageCache[imageUrl] ? ['image'] : CONFIG.SHAPES
const availableShapes =
imageUrl && this.imageCache[imageUrl] ? ['image'] : CONFIG.SHAPES
// 批量创建粒子数组,减少频繁的数组操作
const particles: Firework[] = []
@@ -310,7 +311,8 @@
particle.x = startX
particle.y = startY
// 复杂的速度计算,模拟真实烟花爆炸轨迹
particle.vx = Math.cos(angle) * Math.cos(spread) * speed * (Math.random() * 0.5 + 0.5)
particle.vx =
Math.cos(angle) * Math.cos(spread) * speed * (Math.random() * 0.5 + 0.5)
particle.vy = Math.sin(angle) * speed - 15 // 向上初始速度
particle.color = CONFIG.COLORS[Math.floor(Math.random() * CONFIG.COLORS.length)]
particle.rotation = Math.random() * 360
@@ -463,7 +465,15 @@
case 'oval':
// 绘制椭圆
ctx.value.beginPath()
ctx.value.ellipse(0, 0, SIZES.OVAL.WIDTH / 2, SIZES.OVAL.HEIGHT / 2, 0, 0, Math.PI * 2)
ctx.value.ellipse(
0,
0,
SIZES.OVAL.WIDTH / 2,
SIZES.OVAL.HEIGHT / 2,
0,
0,
Math.PI * 2
)
ctx.value.fill()
break
@@ -35,12 +35,17 @@
>
<div
class="mt-2 h-12 flex-cb rounded-custom-sm bg-g-200/80 px-4 text-sm text-g-700"
:class="isHighlighted(index) ? 'highlighted !bg-theme/70 !text-white' : ''"
:class="
isHighlighted(index) ? 'highlighted !bg-theme/70 !text-white' : ''
"
@click="searchGoPage(item)"
@mouseenter="highlightOnHover(index)"
>
{{ formatMenuTitle(item.meta.title) }}
<ArtSvgIcon v-show="isHighlighted(index)" icon="fluent:arrow-enter-left-20-filled" />
<ArtSvgIcon
v-show="isHighlighted(index)"
icon="fluent:arrow-enter-left-20-filled"
/>
</div>
</div>
</div>
@@ -76,16 +81,24 @@
<div class="dialog-footer box-border flex-c border-t-d pt-4.5 pb-1">
<div class="flex-cc">
<ArtSvgIcon icon="fluent:arrow-enter-left-20-filled" class="keyboard" />
<span class="mr-3.5 text-xs text-g-700">{{ $t('search.selectKeydown') }}</span>
<span class="mr-3.5 text-xs text-g-700">{{
$t('search.selectKeydown')
}}</span>
</div>
<div class="flex-c">
<ArtSvgIcon icon="ri:arrow-up-wide-fill" class="keyboard" />
<ArtSvgIcon icon="ri:arrow-down-wide-fill" class="keyboard" />
<span class="mr-3.5 text-xs text-g-700">{{ $t('search.switchKeydown') }}</span>
<span class="mr-3.5 text-xs text-g-700">{{
$t('search.switchKeydown')
}}</span>
</div>
<div class="flex-c">
<i class="keyboard !w-8 flex-cc"><p class="text-[10px] font-medium">ESC</p></i>
<span class="mr-3.5 text-xs text-g-700">{{ $t('search.exitKeydown') }}</span>
<i class="keyboard !w-8 flex-cc"
><p class="text-[10px] font-medium">ESC</p></i
>
<span class="mr-3.5 text-xs text-g-700">{{
$t('search.exitKeydown')
}}</span>
</div>
</div>
</template>
@@ -18,7 +18,9 @@
<!-- 系统信息 -->
<div class="flex-c c-p" @click="toHome" v-if="isTopMenu">
<ArtLogo class="pl-4.5" />
<p v-if="width >= 1400" class="my-0 mx-2 ml-2 text-lg">{{ AppConfig.systemInfo.name }}</p>
<p v-if="width >= 1400" class="my-0 mx-2 ml-2 text-lg">{{
AppConfig.systemInfo.name
}}</p>
</div>
<ArtLogo
@@ -50,7 +52,9 @@
<!-- 面包屑 -->
<ArtBreadcrumb
v-if="(shouldShowBreadcrumb && isLeftMenu) || (shouldShowBreadcrumb && isDualMenu)"
v-if="
(shouldShowBreadcrumb && isLeftMenu) || (shouldShowBreadcrumb && isDualMenu)
"
/>
<!-- 顶部菜单 -->
@@ -69,7 +73,9 @@
>
<div class="flex-c">
<ArtSvgIcon icon="ri:search-line" class="text-sm text-g-500" />
<span class="ml-1 text-xs font-normal text-g-500">{{ $t('topBar.search.title') }}</span>
<span class="ml-1 text-xs font-normal text-g-500">{{
$t('topBar.search.title')
}}</span>
</div>
<div class="flex-c h-5 px-1.5 text-g-500/80 border border-g-400 rounded">
<ArtSvgIcon v-if="isWindows" icon="vaadin:ctrl-a" class="text-sm" />
@@ -96,7 +102,11 @@
<ArtIconButton icon="ri:translate-2" class="language-btn text-[19px]" />
<template #dropdown>
<ElDropdownMenu>
<div v-for="item in languageOptions" :key="item.value" class="lang-btn-item">
<div
v-for="item in languageOptions"
:key="item.value"
class="lang-btn-item"
>
<ElDropdownItem
:command="item.value"
:class="{ 'is-selected': locale === item.value }"
@@ -126,22 +136,36 @@
class="chat-button relative"
@click="openChat"
>
<div class="breathing-dot absolute top-2 right-2 size-1.5 !bg-success rounded-full"></div>
<div
class="breathing-dot absolute top-2 right-2 size-1.5 !bg-success rounded-full"
></div>
</ArtIconButton>
<!-- 设置按钮 -->
<div v-if="shouldShowSettings">
<ElPopover :visible="showSettingGuide" placement="bottom-start" :width="190" :offset="0">
<ElPopover
:visible="showSettingGuide"
placement="bottom-start"
:width="190"
:offset="0"
>
<template #reference>
<div class="flex-cc">
<ArtIconButton icon="ri:settings-line" class="setting-btn" @click="openSetting" />
<ArtIconButton
icon="ri:settings-line"
class="setting-btn"
@click="openSetting"
/>
</div>
</template>
<template #default>
<p
>{{ $t('topBar.guide.title')
}}<span :style="{ color: systemThemeColor }"> {{ $t('topBar.guide.theme') }} </span
> <span :style="{ color: systemThemeColor }"> {{ $t('topBar.guide.menu') }} </span
}}<span :style="{ color: systemThemeColor }">
{{ $t('topBar.guide.theme') }} </span
>、
<span :style="{ color: systemThemeColor }">
{{ $t('topBar.guide.menu') }} </span
>{{ $t('topBar.guide.description') }}
</p>
</template>
@@ -29,7 +29,9 @@
<span class="block text-sm font-medium text-g-800 truncate">{{
userInfo.userName
}}</span>
<span class="block mt-0.5 text-xs text-g-500 truncate">{{ userInfo.email }}</span>
<span class="block mt-0.5 text-xs text-g-500 truncate">{{
userInfo.email
}}</span>
</div>
</div>
<ul class="py-4 mt-3 border-t border-g-300/80">
@@ -203,7 +203,10 @@
? SCROLL_CONFIG.WHEEL_FAST_STEP
: SCROLL_CONFIG.WHEEL_SLOW_STEP
const scrollDelta = event.deltaY > 0 ? scrollStep : -scrollStep
const targetScroll = Math.max(0, Math.min(scrollLeft + scrollDelta, scrollWidth - clientWidth))
const targetScroll = Math.max(
0,
Math.min(scrollLeft + scrollDelta, scrollWidth - clientWidth)
)
// 立即滚动,无动画
wrapRef.scrollLeft = targetScroll
@@ -9,13 +9,20 @@
<div
v-if="isDualMenu"
class="dual-menu-left"
:style="{ width: dualMenuShowText ? '80px' : '64px', background: getMenuTheme.background }"
:style="{
width: dualMenuShowText ? '80px' : '64px',
background: getMenuTheme.background
}"
>
<ArtLogo class="logo" @click="navigateToHome" />
<ElScrollbar style="height: calc(100% - 135px)">
<ul>
<li v-for="menu in firstLevelMenus" :key="menu.path" @click="handleMenuJump(menu, true)">
<li
v-for="menu in firstLevelMenus"
:key="menu.path"
@click="handleMenuJump(menu, true)"
>
<ElTooltip
class="box-item"
effect="dark"
@@ -48,7 +48,10 @@
{{ formatMenuTitle(item.meta.title) }}
</span>
<div v-if="item.meta.showBadge" class="art-badge" />
<div v-if="item.meta.showTextBadge && (level > 0 || menuOpen)" class="art-text-badge">
<div
v-if="item.meta.showTextBadge && (level > 0 || menuOpen)"
class="art-text-badge"
>
{{ item.meta.showTextBadge }}
</div>
</template>
@@ -41,10 +41,15 @@
class="size-9 leading-9 text-center rounded-lg flex-cc"
:class="[getNoticeStyle(item.type).iconClass]"
>
<ArtSvgIcon class="text-lg !bg-transparent" :icon="getNoticeStyle(item.type).icon" />
<ArtSvgIcon
class="text-lg !bg-transparent"
:icon="getNoticeStyle(item.type).icon"
/>
</div>
<div class="w-[calc(100%-45px)] ml-3.5">
<h4 class="text-sm font-normal leading-5.5 text-g-900">{{ item.title }}</h4>
<h4 class="text-sm font-normal leading-5.5 text-g-900">{{
item.title
}}</h4>
<p class="mt-1.5 text-xs text-g-500">{{ item.time }}</p>
</div>
</li>
@@ -21,7 +21,11 @@
<div v-if="!isLock">
<ElDialog v-model="visible" :width="370" :show-close="false" @open="handleDialogOpen">
<div class="flex-c flex-col">
<img class="w-16 h-16 rounded-full" src="@imgs/user/avatar.webp" alt="用户头像" />
<img
class="w-16 h-16 rounded-full"
src="@imgs/user/avatar.webp"
alt="用户头像"
/>
<div class="mt-7.5 mb-3.5 text-base font-medium">{{ userInfo.userName }}</div>
<ElForm
ref="formRef"
@@ -59,7 +63,11 @@
<!-- 解锁界面 -->
<div v-else class="unlock-content">
<div class="flex-c flex-col w-80">
<img class="w-16 h-16 mt-5 rounded-full" src="@imgs/user/avatar.webp" alt="用户头像" />
<img
class="w-16 h-16 mt-5 rounded-full"
src="@imgs/user/avatar.webp"
alt="用户头像"
/>
<div class="mt-3 mb-3.5 text-base font-medium">
{{ userInfo.userName }}
</div>
@@ -353,7 +361,10 @@
await formRef.value.validate((valid, fields) => {
if (valid) {
const encryptedPassword = CryptoJS.AES.encrypt(formData.password, ENCRYPT_KEY).toString()
const encryptedPassword = CryptoJS.AES.encrypt(
formData.password,
ENCRYPT_KEY
).toString()
userStore.setLockStatus(true)
userStore.setLockPassword(encryptedPassword)
visible.value = false
@@ -8,7 +8,10 @@
:key="item.value"
@click="switchMenuLayouts(item.value)"
>
<div class="box" :class="{ 'is-active': item.value === menuType, 'mt-16': index > 2 }">
<div
class="box"
:class="{ 'is-active': item.value === menuType, 'mt-16': index > 2 }"
>
<img :src="item.img" />
</div>
<p class="name">{{ $t(`setting.menuType.list[${index}]`) }}</p>
@@ -193,7 +193,9 @@
toggleIfDifferent(settingStore.showRefreshButton, config.showRefreshButton, () =>
settingStore.setShowRefreshButton()
)
toggleIfDifferent(settingStore.showCrumbs, config.showCrumbs, () => settingStore.setCrumbs())
toggleIfDifferent(settingStore.showCrumbs, config.showCrumbs, () =>
settingStore.setCrumbs()
)
toggleIfDifferent(settingStore.showLanguage, config.showLanguage, () =>
settingStore.setLanguage()
)
@@ -207,11 +209,15 @@
settingStore.setWatermarkVisible(config.watermarkVisible)
//
toggleIfDifferent(settingStore.autoClose, config.autoClose, () => settingStore.setAutoClose())
toggleIfDifferent(settingStore.autoClose, config.autoClose, () =>
settingStore.setAutoClose()
)
toggleIfDifferent(settingStore.uniqueOpened, config.uniqueOpened, () =>
settingStore.setUniqueOpened()
)
toggleIfDifferent(settingStore.colorWeak, config.colorWeak, () => settingStore.setColorWeak())
toggleIfDifferent(settingStore.colorWeak, config.colorWeak, () =>
settingStore.setColorWeak()
)
//
toggleIfDifferent(settingStore.boxBorderMode, config.boxBorderMode, () =>
@@ -3,7 +3,11 @@
<span class="text-sm">{{ config.label }}</span>
<!-- 开关类型 -->
<ElSwitch v-if="config.type === 'switch'" :model-value="modelValue" @change="handleChange" />
<ElSwitch
v-if="config.type === 'switch'"
:model-value="modelValue"
@change="handleChange"
/>
<!-- 数字输入类型 -->
<ElInputNumber
@@ -21,8 +21,12 @@
<li
class="art-card-xs inline-flex flex-cc h-8 mr-1.5 text-xs c-p hover:text-theme group"
:class="[
item.path === activeTab ? 'activ-tab !text-theme' : 'text-g-600 dark:text-g-800',
tabStyle === 'tab-google' ? 'google-tab relative !h-8 !leading-8 !border-none' : ''
item.path === activeTab
? 'activ-tab !text-theme'
: 'text-g-600 dark:text-g-800',
tabStyle === 'tab-google'
? 'google-tab relative !h-8 !leading-8 !border-none'
: ''
]"
:style="{
padding: item.fixedTab ? '0 10px' : '0 8px 0 12px',
@@ -143,7 +147,9 @@
//
const list = computed(() => store.opened)
const activeTab = computed(() => currentRoute.value.path)
const activeTabIndex = computed(() => list.value.findIndex((tab) => tab.path === activeTab.value))
const activeTabIndex = computed(() =>
list.value.findIndex((tab) => tab.path === activeTab.value)
)
//
const useContextMenu = () => {
@@ -168,15 +174,18 @@
return {
areAllLeftTabsFixed: leftTabs.length > 0 && leftTabs.every((tab) => tab.fixedTab),
areAllRightTabsFixed: rightTabs.length > 0 && rightTabs.every((tab) => tab.fixedTab),
areAllOtherTabsFixed: otherTabs.length > 0 && otherTabs.every((tab) => tab.fixedTab),
areAllRightTabsFixed:
rightTabs.length > 0 && rightTabs.every((tab) => tab.fixedTab),
areAllOtherTabsFixed:
otherTabs.length > 0 && otherTabs.every((tab) => tab.fixedTab),
areAllTabsFixed: list.value.every((tab) => tab.fixedTab)
}
}
//
const menuItems = computed(() => {
const { clickedIndex, currentTab, isLastTab, isOneTab, isCurrentTab } = getClickedTabInfo()
const { clickedIndex, currentTab, isLastTab, isOneTab, isCurrentTab } =
getClickedTabInfo()
const fixedStatus = checkTabsFixedStatus(clickedIndex)
return [
@@ -266,7 +275,8 @@
const { scrollWidth, ulWidth, offsetLeft, curTabRight, targetLeft } = positions
if (
(offsetLeft > Math.abs(scrollState.value.translateX) && curTabRight <= scrollWidth) ||
(offsetLeft > Math.abs(scrollState.value.translateX) &&
curTabRight <= scrollWidth) ||
(scrollState.value.translateX < targetLeft && targetLeft < 0)
) {
return
@@ -313,7 +323,8 @@
const xMax = 0
const xMin = scrollRef.value.offsetWidth - tabsRef.value.offsetWidth
const delta = Math.abs(event.deltaX) > Math.abs(event.deltaY) ? event.deltaX : event.deltaY
const delta =
Math.abs(event.deltaX) > Math.abs(event.deltaY) ? event.deltaX : event.deltaY
scrollState.value.translateX = Math.min(
Math.max(scrollState.value.translateX - delta, xMin),
@@ -57,7 +57,10 @@
v-for="child in item.children"
:key="child.key"
class="menu-item relative mx-1.5 flex-c c-p select-none rounded text-xs transition-colors duration-150 hover:bg-g-200"
:class="{ 'is-disabled': child.disabled, 'has-line': child.showLine }"
:class="{
'is-disabled': child.disabled,
'has-line': child.showLine
}"
:style="menuItemStyle"
@click="handleMenuClick(child)"
>
@@ -12,7 +12,10 @@
@click="search"
:class="showSearchBar ? 'active !bg-theme hover:!bg-theme/80' : ''"
>
<ArtSvgIcon icon="ri:search-line" :class="showSearchBar ? 'text-white' : 'text-g-700'" />
<ArtSvgIcon
icon="ri:search-line"
:class="showSearchBar ? 'text-white' : 'text-g-700'"
/>
</div>
<div
v-if="shouldShow('refresh')"
@@ -50,7 +53,9 @@
</ElDropdown>
<div v-if="shouldShow('fullscreen')" class="button" @click="toggleFullScreen">
<ArtSvgIcon :icon="isFullScreen ? 'ri:fullscreen-exit-line' : 'ri:fullscreen-line'" />
<ArtSvgIcon
:icon="isFullScreen ? 'ri:fullscreen-exit-line' : 'ri:fullscreen-line'"
/>
</div>
<!-- 列设置 -->
@@ -77,7 +82,9 @@
>
<div
class="drag-icon mr-2 h-4.5 flex-cc text-g-500"
:class="item.fixed ? 'cursor-default text-g-300' : 'cursor-move'"
:class="
item.fixed ? 'cursor-default text-g-300' : 'cursor-move'
"
>
<ArtSvgIcon
:icon="item.fixed ? 'ri:unpin-line' : 'ri:drag-move-2-fill'"
@@ -90,7 +97,8 @@
:disabled="item.disabled"
class="flex-1 min-w-0 [&_.el-checkbox__label]:overflow-hidden [&_.el-checkbox__label]:text-ellipsis [&_.el-checkbox__label]:whitespace-nowrap"
>{{
item.label || (item.type === 'selection' ? t('table.selection') : '')
item.label ||
(item.type === 'selection' ? t('table.selection') : '')
}}</ElCheckbox
>
</div>
@@ -112,9 +120,12 @@
<ElCheckbox v-if="showBorder" v-model="isBorder" :value="true">{{
t('table.border')
}}</ElCheckbox>
<ElCheckbox v-if="showHeaderBackground" v-model="isHeaderBackground" :value="true">{{
t('table.headerBackground')
}}</ElCheckbox>
<ElCheckbox
v-if="showHeaderBackground"
v-model="isHeaderBackground"
:value="true"
>{{ t('table.headerBackground') }}</ElCheckbox
>
</div>
</ElPopover>
<slot name="right"></slot>
@@ -91,7 +91,8 @@
const paginationRef = ref<HTMLElement>()
const tableHeaderRef = ref<HTMLElement>()
const tableStore = useTableStore()
const { isBorder, isZebra, tableSize, isFullScreen, isHeaderBackground } = storeToRefs(tableStore)
const { isBorder, isZebra, tableSize, isFullScreen, isHeaderBackground } =
storeToRefs(tableStore)
/** 分页配置接口 */
interface PaginationConfig {
@@ -118,7 +118,11 @@
//
const safeTarget = computed(() => validateNumber(props.target, 'target', 0))
const safeDuration = computed(() =>
clamp(validateNumber(props.duration, 'duration', DEFAULT_DURATION), MIN_DURATION, MAX_DURATION)
clamp(
validateNumber(props.duration, 'duration', DEFAULT_DURATION),
MIN_DURATION,
MAX_DURATION
)
)
const safeDecimals = computed(() =>
clamp(validateNumber(props.decimals, 'decimals', 0), 0, MAX_DECIMALS)
@@ -163,7 +167,12 @@
return `${props.prefix}0${props.suffix}`
}
const formattedNumber = formatNumber(value, safeDecimals.value, props.decimal, props.separator)
const formattedNumber = formatNumber(
value,
safeDecimals.value,
props.decimal,
props.separator
)
return `${props.prefix}${formattedNumber}${props.suffix}`
})
+15 -3
View File
@@ -21,7 +21,11 @@
:style="{ background: color, '--index': index }"
@click="changeThemeColor(color)"
>
<ArtSvgIcon v-if="color === systemThemeColor" icon="ri:check-fill" class="text-white" />
<ArtSvgIcon
v-if="color === systemThemeColor"
icon="ri:check-fill"
class="text-white"
/>
</div>
</div>
<div class="btn palette-btn relative z-[2] h-8 w-8 c-p flex-cc tad-300">
@@ -44,13 +48,21 @@
</div>
<template #dropdown>
<ElDropdownMenu>
<div v-for="lang in languageOptions" :key="lang.value" class="lang-btn-item">
<div
v-for="lang in languageOptions"
:key="lang.value"
class="lang-btn-item"
>
<ElDropdownItem
:command="lang.value"
:class="{ 'is-selected': locale === lang.value }"
>
<span class="menu-txt">{{ lang.label }}</span>
<ArtSvgIcon icon="ri:check-fill" class="text-base" v-if="locale === lang.value" />
<ArtSvgIcon
icon="ri:check-fill"
class="text-base"
v-if="locale === lang.value"
/>
</ElDropdownItem>
</div>
</ElDropdownMenu>
@@ -18,12 +18,18 @@
<!-- 几何装饰元素 -->
<div class="geometric-decorations">
<!-- 基础几何形状 -->
<div class="geo-element circle-outline animate-fade-in-up" style="animation-delay: 0s"></div>
<div
class="geo-element circle-outline animate-fade-in-up"
style="animation-delay: 0s"
></div>
<div
class="geo-element square-rotated animate-fade-in-left"
style="animation-delay: 0s"
></div>
<div class="geo-element circle-small animate-fade-in-up" style="animation-delay: 0.3s"></div>
<div
class="geo-element circle-small animate-fade-in-up"
style="animation-delay: 0.3s"
></div>
<div
class="geo-element square-bottom-right animate-fade-in-right"
@@ -41,7 +47,10 @@
></div>
<!-- 装饰点 -->
<div class="geo-element dot dot-top-left animate-bounce-in" style="animation-delay: 0s"></div>
<div
class="geo-element dot dot-top-left animate-bounce-in"
style="animation-delay: 0s"
></div>
<div
class="geo-element dot dot-top-right animate-bounce-in"
style="animation-delay: 0s"
@@ -475,7 +484,11 @@
width: 80px;
height: 1px;
content: '';
background: linear-gradient(90deg, var(--el-color-primary-light-6), transparent);
background: linear-gradient(
90deg,
var(--el-color-primary-light-6),
transparent
);
opacity: 0;
transform: rotate(50deg);
animation: lineGrow 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
+5 -1
View File
@@ -77,7 +77,11 @@ const appConfig: SystemConfig = {
{ name: 'Left', value: MenuTypeEnum.LEFT, img: configImages.menuLayouts.vertical },
{ name: 'Top', value: MenuTypeEnum.TOP, img: configImages.menuLayouts.horizontal },
{ name: 'Mixed', value: MenuTypeEnum.TOP_LEFT, img: configImages.menuLayouts.mixed },
{ name: 'Dual Column', value: MenuTypeEnum.DUAL_MENU, img: configImages.menuLayouts.dualColumn }
{
name: 'Dual Column',
value: MenuTypeEnum.DUAL_MENU,
img: configImages.menuLayouts.dualColumn
}
],
// 菜单主题列表
themeList: [
+3 -1
View File
@@ -100,7 +100,9 @@ export function useCeremony() {
*/
const currentFestivalData = computed(() => {
const currentDate = useDateFormat(new Date(), 'YYYY-MM-DD').value
return festivalConfigList.find((item) => isDateInRange(currentDate, item.date, item.endDate))
return festivalConfigList.find((item) =>
isDateInRange(currentDate, item.date, item.endDate)
)
})
/**
+3 -1
View File
@@ -421,7 +421,9 @@ function useTableImpl<TApiFn extends (params: any) => Promise<any>>(
}
// 分页获取数据 (重置到第一页) - 专门用于搜索场景
const getDataByPage = async (params?: Partial<TParams>): Promise<ApiResponse<TRecord> | void> => {
const getDataByPage = async (
params?: Partial<TParams>
): Promise<ApiResponse<TRecord> | void> => {
pagination.current = 1
;(searchParams as Record<string, unknown>)[pageKey] = 1
+18 -5
View File
@@ -73,7 +73,13 @@ export const getColumnChecks = <T>(columns: ColumnOption<T>[]) =>
const visibility = getColumnVisibility(col)
if (special) {
return { ...col, prop: special.prop, label: special.label, checked: true, visible: true }
return {
...col,
prop: special.prop,
label: special.label,
checked: true,
visible: true
}
}
return { ...col, checked: visibility, visible: visibility }
})
@@ -114,7 +120,9 @@ export interface DynamicColumnConfig<T = any> {
* @param updates
* @deprecated 使 updateColumn
*/
batchUpdateColumns: (updates: Array<{ prop: string; updates: Partial<ColumnOption<T>> }>) => void
batchUpdateColumns: (
updates: Array<{ prop: string; updates: Partial<ColumnOption<T>> }>
) => void
/**
*
* @param fromIndex
@@ -156,7 +164,9 @@ export function useTableColumns<T = any>(
)
const newChecks = getColumnChecks(newCols).map((c) => {
const key = getColumnKey(c)
const visibility = visibilityMap.has(key) ? visibilityMap.get(key) : getColumnVisibility(c)
const visibility = visibilityMap.has(key)
? visibilityMap.get(key)
: getColumnVisibility(c)
return {
...c,
checked: visibility,
@@ -196,7 +206,9 @@ export function useTableColumns<T = any>(
const next = [...cols]
const columnsToAdd = Array.isArray(column) ? column : [column]
const insertIndex =
typeof index === 'number' && index >= 0 && index <= next.length ? index : next.length
typeof index === 'number' && index >= 0 && index <= next.length
? index
: next.length
// 批量插入
next.splice(insertIndex, 0, ...columnsToAdd)
@@ -302,7 +314,8 @@ export function useTableColumns<T = any>(
/**
*
*/
getColumnConfig: (prop: string) => dynamicColumns.value.find((c) => getColumnKey(c) === prop),
getColumnConfig: (prop: string) =>
dynamicColumns.value.find((c) => getColumnKey(c) === prop),
/**
*
+4 -1
View File
@@ -69,7 +69,10 @@ class TableHeightCalculator {
*
*/
private getHeaderHeight(): number {
return this.options.tableHeaderHeight.value || TableHeightCalculator.DEFAULT_TABLE_HEADER_HEIGHT
return (
this.options.tableHeaderHeight.value ||
TableHeightCalculator.DEFAULT_TABLE_HEADER_HEIGHT
)
}
/**
+4 -1
View File
@@ -152,7 +152,10 @@ export function initializeTheme() {
setElementThemeColor(settingStore.systemThemeColor)
// 设置圆角
document.documentElement.style.setProperty('--custom-radius', `${settingStore.customRadius}rem`)
document.documentElement.style.setProperty(
'--custom-radius',
`${settingStore.customRadius}rem`
)
}
// 应用主题
+6 -27
View File
@@ -48,20 +48,11 @@
"setting": {
"menuType": {
"title": "Menu Layout",
"list": [
"Vertical",
"Horizontal",
"Mixed",
"Dual"
]
"list": ["Vertical", "Horizontal", "Mixed", "Dual"]
},
"theme": {
"title": "Theme Style",
"list": [
"Light",
"Dark",
"System"
]
"list": ["Light", "Dark", "System"]
},
"menu": {
"title": "Menu Style"
@@ -71,17 +62,11 @@
},
"box": {
"title": "Box Style",
"list": [
"Border",
"Shadow"
]
"list": ["Border", "Shadow"]
},
"container": {
"title": "Container Width",
"list": [
"Full",
"Boxed"
]
"list": ["Full", "Boxed"]
},
"basics": {
"title": "Basic Config",
@@ -127,14 +112,8 @@
"notice": {
"title": "Notice",
"btnRead": "Mark as read",
"bar": [
"Notice",
"Message",
"Todo"
],
"text": [
"No"
],
"bar": ["Notice", "Message", "Todo"],
"text": ["No"],
"viewAll": "View all"
},
"worktab": {
+6 -27
View File
@@ -48,20 +48,11 @@
"setting": {
"menuType": {
"title": "菜单布局",
"list": [
"垂直",
"水平",
"混合",
"双列"
]
"list": ["垂直", "水平", "混合", "双列"]
},
"theme": {
"title": "主题风格",
"list": [
"浅色",
"深色",
"系统"
]
"list": ["浅色", "深色", "系统"]
},
"menu": {
"title": "菜单风格"
@@ -71,17 +62,11 @@
},
"box": {
"title": "盒子样式",
"list": [
"边框",
"阴影"
]
"list": ["边框", "阴影"]
},
"container": {
"title": "容器宽度",
"list": [
"铺满",
"定宽"
]
"list": ["铺满", "定宽"]
},
"basics": {
"title": "基础配置",
@@ -127,14 +112,8 @@
"notice": {
"title": "通知",
"btnRead": "标为已读",
"bar": [
"通知",
"消息",
"代办"
],
"text": [
"暂无"
],
"bar": ["通知", "消息", "代办"],
"text": ["暂无"],
"viewAll": "查看全部"
},
"worktab": {
+5 -1
View File
@@ -109,7 +109,11 @@ export class MenuProcessor {
}
// 如果有有效的 component,保留
if (item.component && item.component !== '' && item.component !== RoutesAlias.Layout) {
if (
item.component &&
item.component !== '' &&
item.component !== RoutesAlias.Layout
) {
return true
}
+3 -1
View File
@@ -156,7 +156,9 @@ export const useSettingStore = defineStore(
*
*/
const isShowFireworks = computed((): boolean => {
return festivalDate.value === useCeremony().currentFestivalData.value?.date ? false : true
return festivalDate.value === useCeremony().currentFestivalData.value?.date
? false
: true
})
/**
+7 -3
View File
@@ -65,7 +65,9 @@ export const useWorktabStore = defineStore(
const hasOpenedTabs = computed(() => opened.value.length > 0)
const hasMultipleTabs = computed(() => opened.value.length > 1)
const currentTabIndex = computed(() =>
current.value.path ? opened.value.findIndex((tab) => tab.path === current.value.path) : -1
current.value.path
? opened.value.findIndex((tab) => tab.path === current.value.path)
: -1
)
/**
@@ -216,7 +218,8 @@ export const useWorktabStore = defineStore(
// 如果关闭的是当前激活标签,需要激活其他标签
if (current.value.path === path) {
const newIndex = targetIndex >= opened.value.length ? opened.value.length - 1 : targetIndex
const newIndex =
targetIndex >= opened.value.length ? opened.value.length - 1 : targetIndex
current.value = opened.value[newIndex]
safeRouterPush(current.value)
}
@@ -415,7 +418,8 @@ export const useWorktabStore = defineStore(
if (tab.fixedTab) {
// 固定标签插入到所有固定标签的末尾
const firstNonFixedIndex = opened.value.findIndex((t) => !t.fixedTab)
const insertIndex = firstNonFixedIndex === -1 ? opened.value.length : firstNonFixedIndex
const insertIndex =
firstNonFixedIndex === -1 ? opened.value.length : firstNonFixedIndex
opened.value.splice(insertIndex, 0, tab)
} else {
// 非固定标签插入到所有固定标签后
+4 -1
View File
@@ -109,7 +109,10 @@ declare namespace Api {
/** 用户搜索参数 */
type UserSearchParams = Partial<
Pick<UserListItem, 'id' | 'userName' | 'userGender' | 'userPhone' | 'userEmail' | 'status'> &
Pick<
UserListItem,
'id' | 'userName' | 'userGender' | 'userPhone' | 'userEmail' | 'status'
> &
Api.Common.CommonSearchParams
>
+1 -3
View File
@@ -280,9 +280,7 @@ export interface MapChartProps extends BaseChartProps {
// 双向堆叠柱状图 Props 接口(人口金字塔样式)
export interface BidirectionalBarChartProps
extends BaseChartProps,
AxisDisplayProps,
InteractionProps {
extends BaseChartProps, AxisDisplayProps, InteractionProps {
/** 正向数据(向上显示) */
positiveData: number[]
/** 负向数据(向下显示) */
+5 -1
View File
@@ -67,7 +67,11 @@ axiosInstance.interceptors.request.use(
const { accessToken } = useUserStore()
if (accessToken) request.headers.set('Authorization', accessToken)
if (request.data && !(request.data instanceof FormData) && !request.headers['Content-Type']) {
if (
request.data &&
!(request.data instanceof FormData) &&
!request.headers['Content-Type']
) {
request.headers.set('Content-Type', 'application/json')
request.data = JSON.stringify(request.data)
}
+4 -1
View File
@@ -129,7 +129,10 @@ export default class WebSocketClient {
}
// 发送消息 - 增加消息队列
send(data: string | ArrayBufferLike | Blob | ArrayBufferView, immediate: boolean = false): void {
send(
data: string | ArrayBufferLike | Blob | ArrayBufferView,
immediate: boolean = false
): void {
// 如果要求立即发送且未连接,则直接报错
if (immediate && (!this.ws || this.ws.readyState !== WebSocket.OPEN)) {
console.error('WebSocket未连接,无法立即发送消息')
+3 -1
View File
@@ -77,7 +77,9 @@ class StorageCompatibilityManager {
const storageKeys = Object.keys(localStorage)
const versionPattern = StorageConfig.createVersionPattern()
return storageKeys.some((key) => versionPattern.test(key) && localStorage.getItem(key) !== null)
return storageKeys.some(
(key) => versionPattern.test(key) && localStorage.getItem(key) !== null
)
}
/**
+3 -1
View File
@@ -75,7 +75,9 @@ export function registerResourceErrorHandler() {
const target = event.target as HTMLElement
if (
target &&
(target.tagName === 'IMG' || target.tagName === 'SCRIPT' || target.tagName === 'LINK')
(target.tagName === 'IMG' ||
target.tagName === 'SCRIPT' ||
target.tagName === 'LINK')
) {
console.error('[ResourceError]', {
tagName: target.tagName,
+6 -2
View File
@@ -97,7 +97,9 @@ class VersionManager {
const oldSysKey =
storageKeys.find(
(key) =>
StorageConfig.isVersionedKey(key) && key !== currentVersionPrefix && !key.includes('-')
StorageConfig.isVersionedKey(key) &&
key !== currentVersionPrefix &&
!key.includes('-')
) || null
// 查找旧版本的分离存储键
@@ -121,7 +123,9 @@ class VersionManager {
return upgradeLogList.value.some((item) => {
const itemVersion = this.normalizeVersion(item.version)
return (
item.requireReLogin && itemVersion > normalizedStored && itemVersion <= normalizedCurrent
item.requireReLogin &&
itemVersion > normalizedStored &&
itemVersion <= normalizedCurrent
)
})
}
+5 -1
View File
@@ -185,7 +185,11 @@
],
password: [
{ required: true, validator: validatePassword, trigger: 'blur' },
{ min: PASSWORD_MIN_LENGTH, message: t('register.rule.passwordLength'), trigger: 'blur' }
{
min: PASSWORD_MIN_LENGTH,
message: t('register.rule.passwordLength'),
trigger: 'blur'
}
],
confirmPassword: [{ required: true, validator: validateConfirmPassword, trigger: 'blur' }],
agreement: [{ validator: validateAgreement, trigger: 'change' }]
@@ -11,7 +11,9 @@
<div class="ml-1">
<h3 class="mt-5 text-lg font-medium">用户概述</h3>
<p class="mt-1 text-sm">比上周 <span class="text-success font-medium">+23%</span></p>
<p class="mt-1 text-sm">我们为您创建了多个选项可将它们组合在一起并定制为像素完美的页面</p>
<p class="mt-1 text-sm"
>我们为您创建了多个选项可将它们组合在一起并定制为像素完美的页面</p
>
</div>
<div class="flex-b mt-2">
<div class="flex-1" v-for="(item, index) in list" :key="index">
@@ -3,7 +3,11 @@
<ElCol v-for="(item, index) in dataList" :key="index" :sm="12" :md="6" :lg="6">
<div class="art-card relative flex flex-col justify-center h-35 px-5 mb-5 max-sm:mb-4">
<span class="text-g-700 text-sm">{{ item.des }}</span>
<ArtCountTo class="text-[26px] font-medium mt-2" :target="item.num" :duration="1300" />
<ArtCountTo
class="text-[26px] font-medium mt-2"
:target="item.num"
:duration="1300"
/>
<div class="flex-c mt-1">
<span class="text-xs text-g-600">较上周</span>
<span
@@ -33,7 +33,9 @@
<ElTableColumn label="性别" prop="avatar">
<template #default="scope">
<div style="display: flex; align-items: center">
<span style="margin-left: 10px">{{ scope.row.sex === 1 ? '男' : '女' }}</span>
<span style="margin-left: 10px">{{
scope.row.sex === 1 ? '男' : '女'
}}</span>
</div>
</template>
</ElTableColumn>
+18 -3
View File
@@ -170,7 +170,12 @@
if (form.menuType === 'menu') {
return [
...baseItems,
{ label: '菜单名称', key: 'name', type: 'input', props: { placeholder: '菜单名称' } },
{
label: '菜单名称',
key: 'name',
type: 'input',
props: { placeholder: '菜单名称' }
},
{
label: createLabelTooltip(
'路由地址',
@@ -180,7 +185,12 @@
type: 'input',
props: { placeholder: '如:/dashboard 或 console' }
},
{ label: '权限标识', key: 'label', type: 'input', props: { placeholder: '如:User' } },
{
label: '权限标识',
key: 'label',
type: 'input',
props: { placeholder: '如:User' }
},
{
label: createLabelTooltip(
'组件路径',
@@ -190,7 +200,12 @@
type: 'input',
props: { placeholder: '如:/system/user 或留空' }
},
{ label: '图标', key: 'icon', type: 'input', props: { placeholder: '如:ri:user-line' } },
{
label: '图标',
key: 'icon',
type: 'input',
props: { placeholder: '如:ri:user-line' }
},
{
label: createLabelTooltip(
'角色权限',
@@ -31,7 +31,9 @@
<template #footer>
<ElButton @click="outputSelectedData" style="margin-left: 8px">获取选中数据</ElButton>
<ElButton @click="toggleExpandAll">{{ isExpandAll ? '全部收起' : '全部展开' }}</ElButton>
<ElButton @click="toggleExpandAll">{{
isExpandAll ? '全部收起' : '全部展开'
}}</ElButton>
<ElButton @click="toggleSelectAll" style="margin-left: 8px">{{
isSelectAll ? '取消全选' : '全部选择'
}}</ElButton>
@@ -114,7 +116,9 @@
checked: auth.checked || false
}))
processed.children = processed.children ? [...processed.children, ...authNodes] : authNodes
processed.children = processed.children
? [...processed.children, ...authNodes]
: authNodes
}
//
+21 -4
View File
@@ -4,7 +4,10 @@
<div class="relative flex-b mt-2.5 max-md:block max-md:mt-1">
<div class="w-112 mr-5 max-md:w-full max-md:mr-0">
<div class="art-card-sm relative p-9 pb-6 overflow-hidden text-center">
<img class="absolute top-0 left-0 w-full h-50 object-cover" src="@imgs/user/bg.webp" />
<img
class="absolute top-0 left-0 w-full h-50 object-cover"
src="@imgs/user/bg.webp"
/>
<img
class="relative z-10 w-20 h-20 mt-30 mx-auto object-cover border-2 border-white rounded-full"
src="@imgs/user/avatar.webp"
@@ -62,7 +65,11 @@
<ElInput v-model="form.realName" :disabled="!isEdit" />
</ElFormItem>
<ElFormItem label="性别" prop="sex" class="ml-5">
<ElSelect v-model="form.sex" placeholder="Select" :disabled="!isEdit">
<ElSelect
v-model="form.sex"
placeholder="Select"
:disabled="!isEdit"
>
<ElOption
v-for="item in options"
:key="item.value"
@@ -92,7 +99,12 @@
</ElRow>
<ElFormItem label="个人介绍" prop="des" class="h-32">
<ElInput type="textarea" :rows="4" v-model="form.des" :disabled="!isEdit" />
<ElInput
type="textarea"
:rows="4"
v-model="form.des"
:disabled="!isEdit"
/>
</ElFormItem>
<div class="flex-c justify-end [&_.el-button]:!w-27.5">
@@ -106,7 +118,12 @@
<div class="art-card-sm my-5">
<h1 class="p-4 text-xl font-normal border-b border-g-300">更改密码</h1>
<ElForm :model="pwdForm" class="box-border p-5" label-width="86px" label-position="top">
<ElForm
:model="pwdForm"
class="box-border p-5"
label-width="86px"
label-position="top"
>
<ElFormItem label="当前密码" prop="password">
<ElInput
v-model="pwdForm.password"
+10 -2
View File
@@ -6,11 +6,19 @@
<template>
<div class="user-page art-full-height">
<!-- 搜索栏 -->
<UserSearch v-model="searchForm" @search="handleSearch" @reset="resetSearchParams"></UserSearch>
<UserSearch
v-model="searchForm"
@search="handleSearch"
@reset="resetSearchParams"
></UserSearch>
<ElCard class="art-table-card" shadow="never">
<!-- 表格头部 -->
<ArtTableHeader v-model:columns="columnChecks" :loading="loading" @refresh="refreshData">
<ArtTableHeader
v-model:columns="columnChecks"
:loading="loading"
@refresh="refreshData"
>
<template #left>
<ElSpace wrap>
<ElButton @click="showDialog('add')" v-ripple>新增用户</ElButton>