格式化文档
This commit is contained in:
+2
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"printWidth": 100,
|
"printWidth": 100,
|
||||||
"tabWidth": 2,
|
"tabWidth": 4,
|
||||||
"useTabs": false,
|
"useTabs": true,
|
||||||
"semi": false,
|
"semi": false,
|
||||||
"vueIndentScriptAndStyle": true,
|
"vueIndentScriptAndStyle": true,
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
|
|||||||
+3
-13
@@ -1,10 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "art-design-pro",
|
"name": "art-design-pro",
|
||||||
"version": "0.0.0",
|
"version": "0.1.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.19.0",
|
"node": ">=20.19.0"
|
||||||
"pnpm": ">=8.8.0"
|
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite --open",
|
"dev": "vite --open",
|
||||||
@@ -13,16 +12,7 @@
|
|||||||
"lint": "eslint",
|
"lint": "eslint",
|
||||||
"fix": "eslint --fix",
|
"fix": "eslint --fix",
|
||||||
"lint:prettier": "prettier --write \"**/*.{js,cjs,ts,json,tsx,css,less,scss,vue,html,md}\"",
|
"lint:prettier": "prettier --write \"**/*.{js,cjs,ts,json,tsx,css,less,scss,vue,html,md}\"",
|
||||||
"lint:stylelint": "stylelint \"**/*.{css,scss,vue}\" --fix",
|
"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-staged": {
|
"lint-staged": {
|
||||||
"*.{js,ts,mjs,mts,tsx}": [
|
"*.{js,ts,mjs,mts,tsx}": [
|
||||||
|
|||||||
@@ -151,7 +151,8 @@
|
|||||||
var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
|
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-invert) var(--tw-backdrop-opacity) var(--tw-backdrop-saturate)
|
||||||
var(--tw-backdrop-sepia);
|
var(--tw-backdrop-sepia);
|
||||||
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness) var(--tw-backdrop-contrast)
|
backdrop-filter: var(--tw-backdrop-blur) var(--tw-backdrop-brightness)
|
||||||
var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate) var(--tw-backdrop-invert)
|
var(--tw-backdrop-contrast) var(--tw-backdrop-grayscale) var(--tw-backdrop-hue-rotate)
|
||||||
var(--tw-backdrop-opacity) var(--tw-backdrop-saturate) var(--tw-backdrop-sepia);
|
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">
|
<div class="basic-banner__content">
|
||||||
<!-- title slot -->
|
<!-- title slot -->
|
||||||
<slot name="title">
|
<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>
|
</slot>
|
||||||
|
|
||||||
<!-- subtitle slot -->
|
<!-- subtitle slot -->
|
||||||
<slot name="subtitle">
|
<slot name="subtitle">
|
||||||
<p v-if="subtitle" class="basic-banner__subtitle" :style="{ color: subtitleColor }">{{
|
<p
|
||||||
subtitle
|
v-if="subtitle"
|
||||||
}}</p>
|
class="basic-banner__subtitle"
|
||||||
|
:style="{ color: subtitleColor }"
|
||||||
|
>{{ subtitle }}</p
|
||||||
|
>
|
||||||
</slot>
|
</slot>
|
||||||
|
|
||||||
<!-- button slot -->
|
<!-- button slot -->
|
||||||
@@ -58,7 +63,11 @@
|
|||||||
v-if="imageConfig.src"
|
v-if="imageConfig.src"
|
||||||
class="basic-banner__background-image"
|
class="basic-banner__background-image"
|
||||||
:src="imageConfig.src"
|
: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"
|
loading="lazy"
|
||||||
alt="背景图片"
|
alt="背景图片"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -10,7 +10,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="text-sm font-medium text-danger"
|
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 }}%
|
{{ percentage > 0 ? '+' : '' }}{{ percentage }}%
|
||||||
</div>
|
</div>
|
||||||
@@ -21,7 +24,9 @@
|
|||||||
<div
|
<div
|
||||||
ref="chartRef"
|
ref="chartRef"
|
||||||
class="absolute bottom-0 left-0 right-0 mx-auto"
|
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)` }"
|
:style="{ height: isMiniChart ? '60px' : `calc(${height}rem - 5rem)` }"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -24,7 +24,9 @@
|
|||||||
<div
|
<div
|
||||||
ref="chartRef"
|
ref="chartRef"
|
||||||
class="absolute bottom-0 left-0 right-0 box-border w-full"
|
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)` }"
|
:style="{ height: isMiniChart ? '60px' : `calc(${height}rem - 5rem)` }"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -107,13 +109,15 @@
|
|||||||
offset: 0,
|
offset: 0,
|
||||||
color: props.color
|
color: props.color
|
||||||
? hexToRgba(props.color, 0.2).rgba
|
? hexToRgba(props.color, 0.2).rgba
|
||||||
: hexToRgba(getCssVar('--el-color-primary'), 0.2).rgba
|
: hexToRgba(getCssVar('--el-color-primary'), 0.2)
|
||||||
|
.rgba
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
offset: 1,
|
offset: 1,
|
||||||
color: props.color
|
color: props.color
|
||||||
? hexToRgba(props.color, 0.01).rgba
|
? 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>
|
<template>
|
||||||
<div class="art-card h-32 flex flex-col justify-center px-5">
|
<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">
|
<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>
|
<ArtSvgIcon :icon="icon" class="text-2xl"></ArtSvgIcon>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -4,7 +4,11 @@
|
|||||||
class="art-card h-32 flex-c px-5 transition-transform duration-200 hover:-translate-y-0.5"
|
class="art-card h-32 flex-c px-5 transition-transform duration-200 hover:-translate-y-0.5"
|
||||||
:class="boxStyle"
|
: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>
|
<ArtSvgIcon :icon="icon"></ArtSvgIcon>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
|
|||||||
@@ -18,7 +18,9 @@
|
|||||||
<div class="flex-c gap-3">
|
<div class="flex-c gap-3">
|
||||||
<div class="flex-c gap-2">
|
<div class="flex-c gap-2">
|
||||||
<span class="text-sm">{{ item.content }}</span>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</ElTimelineItem>
|
</ElTimelineItem>
|
||||||
|
|||||||
@@ -135,7 +135,9 @@
|
|||||||
const multiData = props.data as BarDataItem[]
|
const multiData = props.data as BarDataItem[]
|
||||||
return (
|
return (
|
||||||
!multiData.length ||
|
!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],
|
watchSources: [() => props.data, () => props.xAxisData, () => props.colors],
|
||||||
generateOptions: (): EChartsOption => {
|
generateOptions: (): EChartsOption => {
|
||||||
const options: EChartsOption = {
|
const options: EChartsOption = {
|
||||||
grid: getGridWithLegend(props.showLegend && isMultipleData.value, props.legendPosition, {
|
grid: getGridWithLegend(
|
||||||
|
props.showLegend && isMultipleData.value,
|
||||||
|
props.legendPosition,
|
||||||
|
{
|
||||||
top: 15,
|
top: 15,
|
||||||
right: 0,
|
right: 0,
|
||||||
left: 0
|
left: 0
|
||||||
}),
|
}
|
||||||
|
),
|
||||||
tooltip: props.showTooltip ? getTooltipStyle() : undefined,
|
tooltip: props.showTooltip ? getTooltipStyle() : undefined,
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
|
|||||||
@@ -139,7 +139,9 @@
|
|||||||
const multiData = props.data as BarDataItem[]
|
const multiData = props.data as BarDataItem[]
|
||||||
return (
|
return (
|
||||||
!multiData.length ||
|
!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],
|
watchSources: [() => props.data, () => props.xAxisData, () => props.colors],
|
||||||
generateOptions: (): EChartsOption => {
|
generateOptions: (): EChartsOption => {
|
||||||
const options: EChartsOption = {
|
const options: EChartsOption = {
|
||||||
grid: getGridWithLegend(props.showLegend && isMultipleData.value, props.legendPosition, {
|
grid: getGridWithLegend(
|
||||||
|
props.showLegend && isMultipleData.value,
|
||||||
|
props.legendPosition,
|
||||||
|
{
|
||||||
top: 15,
|
top: 15,
|
||||||
right: 0,
|
right: 0,
|
||||||
left: 0
|
left: 0
|
||||||
}),
|
}
|
||||||
|
),
|
||||||
tooltip: props.showTooltip ? getTooltipStyle() : undefined,
|
tooltip: props.showTooltip ? getTooltipStyle() : undefined,
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
|
|||||||
@@ -55,7 +55,8 @@
|
|||||||
return (
|
return (
|
||||||
!props.data?.length ||
|
!props.data?.length ||
|
||||||
props.data.every(
|
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: [
|
series: [
|
||||||
{
|
{
|
||||||
type: 'candlestick',
|
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: {
|
itemStyle: {
|
||||||
color: upColor,
|
color: upColor,
|
||||||
color0: downColor,
|
color0: downColor,
|
||||||
|
|||||||
@@ -193,11 +193,15 @@
|
|||||||
animation: true,
|
animation: true,
|
||||||
animationDuration: isInitial ? 0 : 1300,
|
animationDuration: isInitial ? 0 : 1300,
|
||||||
animationDurationUpdate: 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,
|
top: 15,
|
||||||
right: 15,
|
right: 15,
|
||||||
left: 0
|
left: 0
|
||||||
}),
|
}
|
||||||
|
),
|
||||||
tooltip: props.showTooltip ? getTooltipStyle() : undefined,
|
tooltip: props.showTooltip ? getTooltipStyle() : undefined,
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
@@ -358,7 +362,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 使用 VueUse 的 watchDebounced 优化数据监听(避免频繁更新)
|
// 使用 VueUse 的 watchDebounced 优化数据监听(避免频繁更新)
|
||||||
watch([() => props.data, () => props.xAxisData, () => props.colors], renderChart, { deep: true })
|
watch([() => props.data, () => props.xAxisData, () => props.colors], renderChart, {
|
||||||
|
deep: true
|
||||||
|
})
|
||||||
|
|
||||||
// 生命周期
|
// 生命周期
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|||||||
@@ -36,7 +36,10 @@
|
|||||||
const { chartRef, isDark, getAnimationConfig, getTooltipStyle } = useChartComponent({
|
const { chartRef, isDark, getAnimationConfig, getTooltipStyle } = useChartComponent({
|
||||||
props,
|
props,
|
||||||
checkEmpty: () => {
|
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],
|
watchSources: [() => props.data, () => props.indicator, () => props.colors],
|
||||||
generateOptions: (): EChartsOption => {
|
generateOptions: (): EChartsOption => {
|
||||||
|
|||||||
@@ -52,7 +52,10 @@
|
|||||||
} = useChartComponent({
|
} = useChartComponent({
|
||||||
props,
|
props,
|
||||||
checkEmpty: () => {
|
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],
|
watchSources: [() => props.data, () => props.colors, () => props.symbolSize],
|
||||||
generateOptions: (): EChartsOption => {
|
generateOptions: (): EChartsOption => {
|
||||||
@@ -96,13 +99,17 @@
|
|||||||
itemStyle: {
|
itemStyle: {
|
||||||
color: computedColor,
|
color: computedColor,
|
||||||
shadowBlur: 6,
|
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
|
shadowOffsetY: 2
|
||||||
},
|
},
|
||||||
emphasis: {
|
emphasis: {
|
||||||
itemStyle: {
|
itemStyle: {
|
||||||
shadowBlur: 12,
|
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
|
scale: true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<ElDropdown v-if="hasAnyAuthItem">
|
<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>
|
<template #dropdown>
|
||||||
<ElDropdownMenu>
|
<ElDropdownMenu>
|
||||||
<template v-for="item in list" :key="item.key">
|
<template v-for="item in list" :key="item.key">
|
||||||
|
|||||||
@@ -238,7 +238,8 @@
|
|||||||
handler.value.style.transition = 'none'
|
handler.value.style.transition = 'none'
|
||||||
// 计算拖拽起始位置
|
// 计算拖拽起始位置
|
||||||
state.x =
|
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')
|
emit('handlerMove')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -218,7 +218,9 @@
|
|||||||
|
|
||||||
return columns.map((column) => {
|
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) {
|
if (configWidth) {
|
||||||
return { wch: configWidth }
|
return { wch: configWidth }
|
||||||
@@ -310,7 +312,11 @@
|
|||||||
|
|
||||||
return Promise.resolve()
|
return Promise.resolve()
|
||||||
} catch (error) {
|
} 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 =
|
const exportError =
|
||||||
error instanceof ExportError
|
error instanceof ExportError
|
||||||
? error
|
? error
|
||||||
: new ExportError(`导出失败: ${(error as Error).message}`, 'UNKNOWN_ERROR', error)
|
: new ExportError(
|
||||||
|
`导出失败: ${(error as Error).message}`,
|
||||||
|
'UNKNOWN_ERROR',
|
||||||
|
error
|
||||||
|
)
|
||||||
|
|
||||||
// 触发错误事件
|
// 触发错误事件
|
||||||
emit('export-error', exportError)
|
emit('export-error', exportError)
|
||||||
|
|||||||
@@ -43,7 +43,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 复选框组 -->
|
<!-- 复选框组 -->
|
||||||
<template v-if="item.type === 'checkboxgroup' && getProps(item)?.options">
|
<template
|
||||||
|
v-if="item.type === 'checkboxgroup' && getProps(item)?.options"
|
||||||
|
>
|
||||||
<ElCheckbox
|
<ElCheckbox
|
||||||
v-for="option in getProps(item).options"
|
v-for="option in getProps(item).options"
|
||||||
v-bind="option"
|
v-bind="option"
|
||||||
@@ -52,7 +54,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 单选框组 -->
|
<!-- 单选框组 -->
|
||||||
<template v-if="item.type === 'radiogroup' && getProps(item)?.options">
|
<template
|
||||||
|
v-if="item.type === 'radiogroup' && getProps(item)?.options"
|
||||||
|
>
|
||||||
<ElRadio
|
<ElRadio
|
||||||
v-for="option in getProps(item).options"
|
v-for="option in getProps(item).options"
|
||||||
v-bind="option"
|
v-bind="option"
|
||||||
@@ -61,7 +65,11 @@
|
|||||||
</template>
|
</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" />
|
<component :is="slotFn" />
|
||||||
</template>
|
</template>
|
||||||
</component>
|
</component>
|
||||||
@@ -74,7 +82,12 @@
|
|||||||
:style="actionButtonsStyle"
|
:style="actionButtonsStyle"
|
||||||
>
|
>
|
||||||
<div class="flex gap-2 md:justify-center">
|
<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') }}
|
{{ t('table.form.reset') }}
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
|
|||||||
@@ -43,7 +43,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 复选框组 -->
|
<!-- 复选框组 -->
|
||||||
<template v-if="item.type === 'checkboxgroup' && getProps(item)?.options">
|
<template
|
||||||
|
v-if="item.type === 'checkboxgroup' && getProps(item)?.options"
|
||||||
|
>
|
||||||
<ElCheckbox
|
<ElCheckbox
|
||||||
v-for="option in getProps(item).options"
|
v-for="option in getProps(item).options"
|
||||||
v-bind="option"
|
v-bind="option"
|
||||||
@@ -52,7 +54,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- 单选框组 -->
|
<!-- 单选框组 -->
|
||||||
<template v-if="item.type === 'radiogroup' && getProps(item)?.options">
|
<template
|
||||||
|
v-if="item.type === 'radiogroup' && getProps(item)?.options"
|
||||||
|
>
|
||||||
<ElRadio
|
<ElRadio
|
||||||
v-for="option in getProps(item).options"
|
v-for="option in getProps(item).options"
|
||||||
v-bind="option"
|
v-bind="option"
|
||||||
@@ -61,7 +65,11 @@
|
|||||||
</template>
|
</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" />
|
<component :is="slotFn" />
|
||||||
</template>
|
</template>
|
||||||
</component>
|
</component>
|
||||||
@@ -71,7 +79,12 @@
|
|||||||
<ElCol :xs="24" :sm="24" :md="span" :lg="span" :xl="span" class="action-column">
|
<ElCol :xs="24" :sm="24" :md="span" :lg="span" :xl="span" class="action-column">
|
||||||
<div class="action-buttons-wrapper" :style="actionButtonsStyle">
|
<div class="action-buttons-wrapper" :style="actionButtonsStyle">
|
||||||
<div class="form-buttons">
|
<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') }}
|
{{ t('table.searchBar.reset') }}
|
||||||
</ElButton>
|
</ElButton>
|
||||||
<ElButton
|
<ElButton
|
||||||
@@ -85,7 +98,11 @@
|
|||||||
{{ t('table.searchBar.search') }}
|
{{ t('table.searchBar.search') }}
|
||||||
</ElButton>
|
</ElButton>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="shouldShowExpandToggle" class="filter-toggle" @click="toggleExpand">
|
<div
|
||||||
|
v-if="shouldShowExpandToggle"
|
||||||
|
class="filter-toggle"
|
||||||
|
@click="toggleExpand"
|
||||||
|
>
|
||||||
<span>{{ expandToggleText }}</span>
|
<span>{{ expandToggleText }}</span>
|
||||||
<div class="icon-wrapper">
|
<div class="icon-wrapper">
|
||||||
<ElIcon>
|
<ElIcon>
|
||||||
@@ -298,7 +315,9 @@
|
|||||||
const shouldShowExpandToggle = computed(() => {
|
const shouldShowExpandToggle = computed(() => {
|
||||||
const filteredItems = props.items.filter((item) => !item.hidden)
|
const filteredItems = props.items.filter((item) => !item.hidden)
|
||||||
return (
|
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(
|
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 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) {
|
if (toolbar && toolbarButtons.length > 0) {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
<!-- 系统聊天窗口 -->
|
<!-- 系统聊天窗口 -->
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<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 class="mb-5 flex-cb">
|
||||||
<div>
|
<div>
|
||||||
<span class="text-base font-medium">Art Bot</span>
|
<span class="text-base font-medium">Art Bot</span>
|
||||||
@@ -34,7 +38,10 @@
|
|||||||
>
|
>
|
||||||
<ElAvatar :size="32" :src="message.avatar" class="shrink-0" />
|
<ElAvatar :size="32" :src="message.avatar" class="shrink-0" />
|
||||||
<div
|
<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
|
<div
|
||||||
:class="[
|
:class="[
|
||||||
@@ -48,7 +55,9 @@
|
|||||||
<div
|
<div
|
||||||
:class="[
|
:class="[
|
||||||
'rounded-md px-3.5 py-2.5 text-sm leading-[1.4] text-g-900',
|
'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
|
>{{ message.content }}</div
|
||||||
>
|
>
|
||||||
@@ -71,16 +80,23 @@
|
|||||||
<div class="flex gap-2 py-2">
|
<div class="flex gap-2 py-2">
|
||||||
<ElButton :icon="Paperclip" circle plain />
|
<ElButton :icon="Paperclip" circle plain />
|
||||||
<ElButton :icon="Picture" circle plain />
|
<ElButton :icon="Picture" circle plain />
|
||||||
<ElButton type="primary" @click="sendMessage" v-ripple>发送</ElButton>
|
<ElButton type="primary" @click="sendMessage" v-ripple
|
||||||
|
>发送</ElButton
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ElInput>
|
</ElInput>
|
||||||
<div class="mt-3 flex-cb">
|
<div class="mt-3 flex-cb">
|
||||||
<div class="flex-c">
|
<div class="flex-c">
|
||||||
<ArtSvgIcon icon="ri:image-line" class="mr-5 c-p text-g-600 text-lg" />
|
<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>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -146,7 +162,8 @@
|
|||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
sender: BOT_NAME,
|
sender: BOT_NAME,
|
||||||
content: '好的,我来为您介绍系统的主要功能。首先,您可以通过左侧菜单访问不同的功能模块...',
|
content:
|
||||||
|
'好的,我来为您介绍系统的主要功能。首先,您可以通过左侧菜单访问不同的功能模块...',
|
||||||
time: '10:02',
|
time: '10:02',
|
||||||
isMe: false,
|
isMe: false,
|
||||||
avatar: aiAvatar
|
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"
|
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)"
|
@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
|
<ArtSvgIcon
|
||||||
class="text-xl"
|
class="text-xl"
|
||||||
:icon="application.icon"
|
:icon="application.icon"
|
||||||
@@ -37,7 +39,9 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<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>
|
<p class="mt-1 text-xs text-g-600">{{ application.description }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -292,7 +292,8 @@
|
|||||||
const startY = this.canvasHeight
|
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[] = []
|
const particles: Firework[] = []
|
||||||
@@ -310,7 +311,8 @@
|
|||||||
particle.x = startX
|
particle.x = startX
|
||||||
particle.y = startY
|
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.vy = Math.sin(angle) * speed - 15 // 向上初始速度
|
||||||
particle.color = CONFIG.COLORS[Math.floor(Math.random() * CONFIG.COLORS.length)]
|
particle.color = CONFIG.COLORS[Math.floor(Math.random() * CONFIG.COLORS.length)]
|
||||||
particle.rotation = Math.random() * 360
|
particle.rotation = Math.random() * 360
|
||||||
@@ -463,7 +465,15 @@
|
|||||||
case 'oval':
|
case 'oval':
|
||||||
// 绘制椭圆
|
// 绘制椭圆
|
||||||
ctx.value.beginPath()
|
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()
|
ctx.value.fill()
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|||||||
@@ -35,12 +35,17 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="mt-2 h-12 flex-cb rounded-custom-sm bg-g-200/80 px-4 text-sm text-g-700"
|
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)"
|
@click="searchGoPage(item)"
|
||||||
@mouseenter="highlightOnHover(index)"
|
@mouseenter="highlightOnHover(index)"
|
||||||
>
|
>
|
||||||
{{ formatMenuTitle(item.meta.title) }}
|
{{ 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>
|
</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="dialog-footer box-border flex-c border-t-d pt-4.5 pb-1">
|
||||||
<div class="flex-cc">
|
<div class="flex-cc">
|
||||||
<ArtSvgIcon icon="fluent:arrow-enter-left-20-filled" class="keyboard" />
|
<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>
|
||||||
<div class="flex-c">
|
<div class="flex-c">
|
||||||
<ArtSvgIcon icon="ri:arrow-up-wide-fill" class="keyboard" />
|
<ArtSvgIcon icon="ri:arrow-up-wide-fill" class="keyboard" />
|
||||||
<ArtSvgIcon icon="ri:arrow-down-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>
|
||||||
<div class="flex-c">
|
<div class="flex-c">
|
||||||
<i class="keyboard !w-8 flex-cc"><p class="text-[10px] font-medium">ESC</p></i>
|
<i class="keyboard !w-8 flex-cc"
|
||||||
<span class="mr-3.5 text-xs text-g-700">{{ $t('search.exitKeydown') }}</span>
|
><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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -18,7 +18,9 @@
|
|||||||
<!-- 系统信息 -->
|
<!-- 系统信息 -->
|
||||||
<div class="flex-c c-p" @click="toHome" v-if="isTopMenu">
|
<div class="flex-c c-p" @click="toHome" v-if="isTopMenu">
|
||||||
<ArtLogo class="pl-4.5" />
|
<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>
|
</div>
|
||||||
|
|
||||||
<ArtLogo
|
<ArtLogo
|
||||||
@@ -50,7 +52,9 @@
|
|||||||
|
|
||||||
<!-- 面包屑 -->
|
<!-- 面包屑 -->
|
||||||
<ArtBreadcrumb
|
<ArtBreadcrumb
|
||||||
v-if="(shouldShowBreadcrumb && isLeftMenu) || (shouldShowBreadcrumb && isDualMenu)"
|
v-if="
|
||||||
|
(shouldShowBreadcrumb && isLeftMenu) || (shouldShowBreadcrumb && isDualMenu)
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<!-- 顶部菜单 -->
|
<!-- 顶部菜单 -->
|
||||||
@@ -69,7 +73,9 @@
|
|||||||
>
|
>
|
||||||
<div class="flex-c">
|
<div class="flex-c">
|
||||||
<ArtSvgIcon icon="ri:search-line" class="text-sm text-g-500" />
|
<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>
|
||||||
<div class="flex-c h-5 px-1.5 text-g-500/80 border border-g-400 rounded">
|
<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" />
|
<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]" />
|
<ArtIconButton icon="ri:translate-2" class="language-btn text-[19px]" />
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<ElDropdownMenu>
|
<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
|
<ElDropdownItem
|
||||||
:command="item.value"
|
:command="item.value"
|
||||||
:class="{ 'is-selected': locale === item.value }"
|
:class="{ 'is-selected': locale === item.value }"
|
||||||
@@ -126,22 +136,36 @@
|
|||||||
class="chat-button relative"
|
class="chat-button relative"
|
||||||
@click="openChat"
|
@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>
|
</ArtIconButton>
|
||||||
|
|
||||||
<!-- 设置按钮 -->
|
<!-- 设置按钮 -->
|
||||||
<div v-if="shouldShowSettings">
|
<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>
|
<template #reference>
|
||||||
<div class="flex-cc">
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #default>
|
<template #default>
|
||||||
<p
|
<p
|
||||||
>{{ $t('topBar.guide.title')
|
>{{ $t('topBar.guide.title')
|
||||||
}}<span :style="{ color: systemThemeColor }"> {{ $t('topBar.guide.theme') }} </span
|
}}<span :style="{ color: systemThemeColor }">
|
||||||
>、 <span :style="{ color: systemThemeColor }"> {{ $t('topBar.guide.menu') }} </span
|
{{ $t('topBar.guide.theme') }} </span
|
||||||
|
>、
|
||||||
|
<span :style="{ color: systemThemeColor }">
|
||||||
|
{{ $t('topBar.guide.menu') }} </span
|
||||||
>{{ $t('topBar.guide.description') }}
|
>{{ $t('topBar.guide.description') }}
|
||||||
</p>
|
</p>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -29,7 +29,9 @@
|
|||||||
<span class="block text-sm font-medium text-g-800 truncate">{{
|
<span class="block text-sm font-medium text-g-800 truncate">{{
|
||||||
userInfo.userName
|
userInfo.userName
|
||||||
}}</span>
|
}}</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>
|
||||||
</div>
|
</div>
|
||||||
<ul class="py-4 mt-3 border-t border-g-300/80">
|
<ul class="py-4 mt-3 border-t border-g-300/80">
|
||||||
|
|||||||
@@ -203,7 +203,10 @@
|
|||||||
? SCROLL_CONFIG.WHEEL_FAST_STEP
|
? SCROLL_CONFIG.WHEEL_FAST_STEP
|
||||||
: SCROLL_CONFIG.WHEEL_SLOW_STEP
|
: SCROLL_CONFIG.WHEEL_SLOW_STEP
|
||||||
const scrollDelta = event.deltaY > 0 ? scrollStep : -scrollStep
|
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
|
wrapRef.scrollLeft = targetScroll
|
||||||
|
|||||||
@@ -9,13 +9,20 @@
|
|||||||
<div
|
<div
|
||||||
v-if="isDualMenu"
|
v-if="isDualMenu"
|
||||||
class="dual-menu-left"
|
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" />
|
<ArtLogo class="logo" @click="navigateToHome" />
|
||||||
|
|
||||||
<ElScrollbar style="height: calc(100% - 135px)">
|
<ElScrollbar style="height: calc(100% - 135px)">
|
||||||
<ul>
|
<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
|
<ElTooltip
|
||||||
class="box-item"
|
class="box-item"
|
||||||
effect="dark"
|
effect="dark"
|
||||||
|
|||||||
@@ -48,7 +48,10 @@
|
|||||||
{{ formatMenuTitle(item.meta.title) }}
|
{{ formatMenuTitle(item.meta.title) }}
|
||||||
</span>
|
</span>
|
||||||
<div v-if="item.meta.showBadge" class="art-badge" />
|
<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 }}
|
{{ item.meta.showTextBadge }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -41,10 +41,15 @@
|
|||||||
class="size-9 leading-9 text-center rounded-lg flex-cc"
|
class="size-9 leading-9 text-center rounded-lg flex-cc"
|
||||||
:class="[getNoticeStyle(item.type).iconClass]"
|
: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>
|
||||||
<div class="w-[calc(100%-45px)] ml-3.5">
|
<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>
|
<p class="mt-1.5 text-xs text-g-500">{{ item.time }}</p>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -21,7 +21,11 @@
|
|||||||
<div v-if="!isLock">
|
<div v-if="!isLock">
|
||||||
<ElDialog v-model="visible" :width="370" :show-close="false" @open="handleDialogOpen">
|
<ElDialog v-model="visible" :width="370" :show-close="false" @open="handleDialogOpen">
|
||||||
<div class="flex-c flex-col">
|
<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>
|
<div class="mt-7.5 mb-3.5 text-base font-medium">{{ userInfo.userName }}</div>
|
||||||
<ElForm
|
<ElForm
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
@@ -59,7 +63,11 @@
|
|||||||
<!-- 解锁界面 -->
|
<!-- 解锁界面 -->
|
||||||
<div v-else class="unlock-content">
|
<div v-else class="unlock-content">
|
||||||
<div class="flex-c flex-col w-80">
|
<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">
|
<div class="mt-3 mb-3.5 text-base font-medium">
|
||||||
{{ userInfo.userName }}
|
{{ userInfo.userName }}
|
||||||
</div>
|
</div>
|
||||||
@@ -353,7 +361,10 @@
|
|||||||
|
|
||||||
await formRef.value.validate((valid, fields) => {
|
await formRef.value.validate((valid, fields) => {
|
||||||
if (valid) {
|
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.setLockStatus(true)
|
||||||
userStore.setLockPassword(encryptedPassword)
|
userStore.setLockPassword(encryptedPassword)
|
||||||
visible.value = false
|
visible.value = false
|
||||||
|
|||||||
@@ -8,7 +8,10 @@
|
|||||||
:key="item.value"
|
:key="item.value"
|
||||||
@click="switchMenuLayouts(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" />
|
<img :src="item.img" />
|
||||||
</div>
|
</div>
|
||||||
<p class="name">{{ $t(`setting.menuType.list[${index}]`) }}</p>
|
<p class="name">{{ $t(`setting.menuType.list[${index}]`) }}</p>
|
||||||
|
|||||||
@@ -193,7 +193,9 @@
|
|||||||
toggleIfDifferent(settingStore.showRefreshButton, config.showRefreshButton, () =>
|
toggleIfDifferent(settingStore.showRefreshButton, config.showRefreshButton, () =>
|
||||||
settingStore.setShowRefreshButton()
|
settingStore.setShowRefreshButton()
|
||||||
)
|
)
|
||||||
toggleIfDifferent(settingStore.showCrumbs, config.showCrumbs, () => settingStore.setCrumbs())
|
toggleIfDifferent(settingStore.showCrumbs, config.showCrumbs, () =>
|
||||||
|
settingStore.setCrumbs()
|
||||||
|
)
|
||||||
toggleIfDifferent(settingStore.showLanguage, config.showLanguage, () =>
|
toggleIfDifferent(settingStore.showLanguage, config.showLanguage, () =>
|
||||||
settingStore.setLanguage()
|
settingStore.setLanguage()
|
||||||
)
|
)
|
||||||
@@ -207,11 +209,15 @@
|
|||||||
settingStore.setWatermarkVisible(config.watermarkVisible)
|
settingStore.setWatermarkVisible(config.watermarkVisible)
|
||||||
|
|
||||||
// 功能设置
|
// 功能设置
|
||||||
toggleIfDifferent(settingStore.autoClose, config.autoClose, () => settingStore.setAutoClose())
|
toggleIfDifferent(settingStore.autoClose, config.autoClose, () =>
|
||||||
|
settingStore.setAutoClose()
|
||||||
|
)
|
||||||
toggleIfDifferent(settingStore.uniqueOpened, config.uniqueOpened, () =>
|
toggleIfDifferent(settingStore.uniqueOpened, config.uniqueOpened, () =>
|
||||||
settingStore.setUniqueOpened()
|
settingStore.setUniqueOpened()
|
||||||
)
|
)
|
||||||
toggleIfDifferent(settingStore.colorWeak, config.colorWeak, () => settingStore.setColorWeak())
|
toggleIfDifferent(settingStore.colorWeak, config.colorWeak, () =>
|
||||||
|
settingStore.setColorWeak()
|
||||||
|
)
|
||||||
|
|
||||||
// 样式设置
|
// 样式设置
|
||||||
toggleIfDifferent(settingStore.boxBorderMode, config.boxBorderMode, () =>
|
toggleIfDifferent(settingStore.boxBorderMode, config.boxBorderMode, () =>
|
||||||
|
|||||||
@@ -3,7 +3,11 @@
|
|||||||
<span class="text-sm">{{ config.label }}</span>
|
<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
|
<ElInputNumber
|
||||||
|
|||||||
@@ -21,8 +21,12 @@
|
|||||||
<li
|
<li
|
||||||
class="art-card-xs inline-flex flex-cc h-8 mr-1.5 text-xs c-p hover:text-theme group"
|
class="art-card-xs inline-flex flex-cc h-8 mr-1.5 text-xs c-p hover:text-theme group"
|
||||||
:class="[
|
:class="[
|
||||||
item.path === activeTab ? 'activ-tab !text-theme' : 'text-g-600 dark:text-g-800',
|
item.path === activeTab
|
||||||
tabStyle === 'tab-google' ? 'google-tab relative !h-8 !leading-8 !border-none' : ''
|
? 'activ-tab !text-theme'
|
||||||
|
: 'text-g-600 dark:text-g-800',
|
||||||
|
tabStyle === 'tab-google'
|
||||||
|
? 'google-tab relative !h-8 !leading-8 !border-none'
|
||||||
|
: ''
|
||||||
]"
|
]"
|
||||||
:style="{
|
:style="{
|
||||||
padding: item.fixedTab ? '0 10px' : '0 8px 0 12px',
|
padding: item.fixedTab ? '0 10px' : '0 8px 0 12px',
|
||||||
@@ -143,7 +147,9 @@
|
|||||||
// 计算属性
|
// 计算属性
|
||||||
const list = computed(() => store.opened)
|
const list = computed(() => store.opened)
|
||||||
const activeTab = computed(() => currentRoute.value.path)
|
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 = () => {
|
const useContextMenu = () => {
|
||||||
@@ -168,15 +174,18 @@
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
areAllLeftTabsFixed: leftTabs.length > 0 && leftTabs.every((tab) => tab.fixedTab),
|
areAllLeftTabsFixed: leftTabs.length > 0 && leftTabs.every((tab) => tab.fixedTab),
|
||||||
areAllRightTabsFixed: rightTabs.length > 0 && rightTabs.every((tab) => tab.fixedTab),
|
areAllRightTabsFixed:
|
||||||
areAllOtherTabsFixed: otherTabs.length > 0 && otherTabs.every((tab) => tab.fixedTab),
|
rightTabs.length > 0 && rightTabs.every((tab) => tab.fixedTab),
|
||||||
|
areAllOtherTabsFixed:
|
||||||
|
otherTabs.length > 0 && otherTabs.every((tab) => tab.fixedTab),
|
||||||
areAllTabsFixed: list.value.every((tab) => tab.fixedTab)
|
areAllTabsFixed: list.value.every((tab) => tab.fixedTab)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 右键菜单选项
|
// 右键菜单选项
|
||||||
const menuItems = computed(() => {
|
const menuItems = computed(() => {
|
||||||
const { clickedIndex, currentTab, isLastTab, isOneTab, isCurrentTab } = getClickedTabInfo()
|
const { clickedIndex, currentTab, isLastTab, isOneTab, isCurrentTab } =
|
||||||
|
getClickedTabInfo()
|
||||||
const fixedStatus = checkTabsFixedStatus(clickedIndex)
|
const fixedStatus = checkTabsFixedStatus(clickedIndex)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@@ -266,7 +275,8 @@
|
|||||||
const { scrollWidth, ulWidth, offsetLeft, curTabRight, targetLeft } = positions
|
const { scrollWidth, ulWidth, offsetLeft, curTabRight, targetLeft } = positions
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(offsetLeft > Math.abs(scrollState.value.translateX) && curTabRight <= scrollWidth) ||
|
(offsetLeft > Math.abs(scrollState.value.translateX) &&
|
||||||
|
curTabRight <= scrollWidth) ||
|
||||||
(scrollState.value.translateX < targetLeft && targetLeft < 0)
|
(scrollState.value.translateX < targetLeft && targetLeft < 0)
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
@@ -313,7 +323,8 @@
|
|||||||
|
|
||||||
const xMax = 0
|
const xMax = 0
|
||||||
const xMin = scrollRef.value.offsetWidth - tabsRef.value.offsetWidth
|
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(
|
scrollState.value.translateX = Math.min(
|
||||||
Math.max(scrollState.value.translateX - delta, xMin),
|
Math.max(scrollState.value.translateX - delta, xMin),
|
||||||
|
|||||||
@@ -57,7 +57,10 @@
|
|||||||
v-for="child in item.children"
|
v-for="child in item.children"
|
||||||
:key="child.key"
|
: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="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"
|
:style="menuItemStyle"
|
||||||
@click="handleMenuClick(child)"
|
@click="handleMenuClick(child)"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -12,7 +12,10 @@
|
|||||||
@click="search"
|
@click="search"
|
||||||
:class="showSearchBar ? 'active !bg-theme hover:!bg-theme/80' : ''"
|
: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>
|
||||||
<div
|
<div
|
||||||
v-if="shouldShow('refresh')"
|
v-if="shouldShow('refresh')"
|
||||||
@@ -50,7 +53,9 @@
|
|||||||
</ElDropdown>
|
</ElDropdown>
|
||||||
|
|
||||||
<div v-if="shouldShow('fullscreen')" class="button" @click="toggleFullScreen">
|
<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>
|
</div>
|
||||||
|
|
||||||
<!-- 列设置 -->
|
<!-- 列设置 -->
|
||||||
@@ -77,7 +82,9 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="drag-icon mr-2 h-4.5 flex-cc text-g-500"
|
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
|
<ArtSvgIcon
|
||||||
:icon="item.fixed ? 'ri:unpin-line' : 'ri:drag-move-2-fill'"
|
:icon="item.fixed ? 'ri:unpin-line' : 'ri:drag-move-2-fill'"
|
||||||
@@ -90,7 +97,8 @@
|
|||||||
:disabled="item.disabled"
|
:disabled="item.disabled"
|
||||||
class="flex-1 min-w-0 [&_.el-checkbox__label]:overflow-hidden [&_.el-checkbox__label]:text-ellipsis [&_.el-checkbox__label]:whitespace-nowrap"
|
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
|
}}</ElCheckbox
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
@@ -112,9 +120,12 @@
|
|||||||
<ElCheckbox v-if="showBorder" v-model="isBorder" :value="true">{{
|
<ElCheckbox v-if="showBorder" v-model="isBorder" :value="true">{{
|
||||||
t('table.border')
|
t('table.border')
|
||||||
}}</ElCheckbox>
|
}}</ElCheckbox>
|
||||||
<ElCheckbox v-if="showHeaderBackground" v-model="isHeaderBackground" :value="true">{{
|
<ElCheckbox
|
||||||
t('table.headerBackground')
|
v-if="showHeaderBackground"
|
||||||
}}</ElCheckbox>
|
v-model="isHeaderBackground"
|
||||||
|
:value="true"
|
||||||
|
>{{ t('table.headerBackground') }}</ElCheckbox
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</ElPopover>
|
</ElPopover>
|
||||||
<slot name="right"></slot>
|
<slot name="right"></slot>
|
||||||
|
|||||||
@@ -91,7 +91,8 @@
|
|||||||
const paginationRef = ref<HTMLElement>()
|
const paginationRef = ref<HTMLElement>()
|
||||||
const tableHeaderRef = ref<HTMLElement>()
|
const tableHeaderRef = ref<HTMLElement>()
|
||||||
const tableStore = useTableStore()
|
const tableStore = useTableStore()
|
||||||
const { isBorder, isZebra, tableSize, isFullScreen, isHeaderBackground } = storeToRefs(tableStore)
|
const { isBorder, isZebra, tableSize, isFullScreen, isHeaderBackground } =
|
||||||
|
storeToRefs(tableStore)
|
||||||
|
|
||||||
/** 分页配置接口 */
|
/** 分页配置接口 */
|
||||||
interface PaginationConfig {
|
interface PaginationConfig {
|
||||||
|
|||||||
@@ -118,7 +118,11 @@
|
|||||||
// 安全计算值
|
// 安全计算值
|
||||||
const safeTarget = computed(() => validateNumber(props.target, 'target', 0))
|
const safeTarget = computed(() => validateNumber(props.target, 'target', 0))
|
||||||
const safeDuration = computed(() =>
|
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(() =>
|
const safeDecimals = computed(() =>
|
||||||
clamp(validateNumber(props.decimals, 'decimals', 0), 0, MAX_DECIMALS)
|
clamp(validateNumber(props.decimals, 'decimals', 0), 0, MAX_DECIMALS)
|
||||||
@@ -163,7 +167,12 @@
|
|||||||
return `${props.prefix}0${props.suffix}`
|
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}`
|
return `${props.prefix}${formattedNumber}${props.suffix}`
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,11 @@
|
|||||||
:style="{ background: color, '--index': index }"
|
:style="{ background: color, '--index': index }"
|
||||||
@click="changeThemeColor(color)"
|
@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>
|
</div>
|
||||||
<div class="btn palette-btn relative z-[2] h-8 w-8 c-p flex-cc tad-300">
|
<div class="btn palette-btn relative z-[2] h-8 w-8 c-p flex-cc tad-300">
|
||||||
@@ -44,13 +48,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<ElDropdownMenu>
|
<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
|
<ElDropdownItem
|
||||||
:command="lang.value"
|
:command="lang.value"
|
||||||
:class="{ 'is-selected': locale === lang.value }"
|
:class="{ 'is-selected': locale === lang.value }"
|
||||||
>
|
>
|
||||||
<span class="menu-txt">{{ lang.label }}</span>
|
<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>
|
</ElDropdownItem>
|
||||||
</div>
|
</div>
|
||||||
</ElDropdownMenu>
|
</ElDropdownMenu>
|
||||||
|
|||||||
@@ -18,12 +18,18 @@
|
|||||||
<!-- 几何装饰元素 -->
|
<!-- 几何装饰元素 -->
|
||||||
<div class="geometric-decorations">
|
<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
|
<div
|
||||||
class="geo-element square-rotated animate-fade-in-left"
|
class="geo-element square-rotated animate-fade-in-left"
|
||||||
style="animation-delay: 0s"
|
style="animation-delay: 0s"
|
||||||
></div>
|
></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
|
<div
|
||||||
class="geo-element square-bottom-right animate-fade-in-right"
|
class="geo-element square-bottom-right animate-fade-in-right"
|
||||||
@@ -41,7 +47,10 @@
|
|||||||
></div>
|
></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
|
<div
|
||||||
class="geo-element dot dot-top-right animate-bounce-in"
|
class="geo-element dot dot-top-right animate-bounce-in"
|
||||||
style="animation-delay: 0s"
|
style="animation-delay: 0s"
|
||||||
@@ -475,7 +484,11 @@
|
|||||||
width: 80px;
|
width: 80px;
|
||||||
height: 1px;
|
height: 1px;
|
||||||
content: '';
|
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;
|
opacity: 0;
|
||||||
transform: rotate(50deg);
|
transform: rotate(50deg);
|
||||||
animation: lineGrow 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
|
animation: lineGrow 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;
|
||||||
|
|||||||
+5
-1
@@ -77,7 +77,11 @@ const appConfig: SystemConfig = {
|
|||||||
{ name: 'Left', value: MenuTypeEnum.LEFT, img: configImages.menuLayouts.vertical },
|
{ name: 'Left', value: MenuTypeEnum.LEFT, img: configImages.menuLayouts.vertical },
|
||||||
{ name: 'Top', value: MenuTypeEnum.TOP, img: configImages.menuLayouts.horizontal },
|
{ name: 'Top', value: MenuTypeEnum.TOP, img: configImages.menuLayouts.horizontal },
|
||||||
{ name: 'Mixed', value: MenuTypeEnum.TOP_LEFT, img: configImages.menuLayouts.mixed },
|
{ 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: [
|
themeList: [
|
||||||
|
|||||||
@@ -100,7 +100,9 @@ export function useCeremony() {
|
|||||||
*/
|
*/
|
||||||
const currentFestivalData = computed(() => {
|
const currentFestivalData = computed(() => {
|
||||||
const currentDate = useDateFormat(new Date(), 'YYYY-MM-DD').value
|
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)
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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
|
pagination.current = 1
|
||||||
;(searchParams as Record<string, unknown>)[pageKey] = 1
|
;(searchParams as Record<string, unknown>)[pageKey] = 1
|
||||||
|
|
||||||
|
|||||||
@@ -73,7 +73,13 @@ export const getColumnChecks = <T>(columns: ColumnOption<T>[]) =>
|
|||||||
const visibility = getColumnVisibility(col)
|
const visibility = getColumnVisibility(col)
|
||||||
|
|
||||||
if (special) {
|
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 }
|
return { ...col, checked: visibility, visible: visibility }
|
||||||
})
|
})
|
||||||
@@ -114,7 +120,9 @@ export interface DynamicColumnConfig<T = any> {
|
|||||||
* @param updates 列更新配置
|
* @param updates 列更新配置
|
||||||
* @deprecated 推荐使用 updateColumn 的数组模式
|
* @deprecated 推荐使用 updateColumn 的数组模式
|
||||||
*/
|
*/
|
||||||
batchUpdateColumns: (updates: Array<{ prop: string; updates: Partial<ColumnOption<T>> }>) => void
|
batchUpdateColumns: (
|
||||||
|
updates: Array<{ prop: string; updates: Partial<ColumnOption<T>> }>
|
||||||
|
) => void
|
||||||
/**
|
/**
|
||||||
* 重新排序列
|
* 重新排序列
|
||||||
* @param fromIndex 源索引
|
* @param fromIndex 源索引
|
||||||
@@ -156,7 +164,9 @@ export function useTableColumns<T = any>(
|
|||||||
)
|
)
|
||||||
const newChecks = getColumnChecks(newCols).map((c) => {
|
const newChecks = getColumnChecks(newCols).map((c) => {
|
||||||
const key = getColumnKey(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 {
|
return {
|
||||||
...c,
|
...c,
|
||||||
checked: visibility,
|
checked: visibility,
|
||||||
@@ -196,7 +206,9 @@ export function useTableColumns<T = any>(
|
|||||||
const next = [...cols]
|
const next = [...cols]
|
||||||
const columnsToAdd = Array.isArray(column) ? column : [column]
|
const columnsToAdd = Array.isArray(column) ? column : [column]
|
||||||
const insertIndex =
|
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)
|
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),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取所有列配置
|
* 获取所有列配置
|
||||||
|
|||||||
@@ -69,7 +69,10 @@ class TableHeightCalculator {
|
|||||||
* 获取表格头部高度
|
* 获取表格头部高度
|
||||||
*/
|
*/
|
||||||
private getHeaderHeight(): number {
|
private getHeaderHeight(): number {
|
||||||
return this.options.tableHeaderHeight.value || TableHeightCalculator.DEFAULT_TABLE_HEADER_HEIGHT
|
return (
|
||||||
|
this.options.tableHeaderHeight.value ||
|
||||||
|
TableHeightCalculator.DEFAULT_TABLE_HEADER_HEIGHT
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -152,7 +152,10 @@ export function initializeTheme() {
|
|||||||
setElementThemeColor(settingStore.systemThemeColor)
|
setElementThemeColor(settingStore.systemThemeColor)
|
||||||
|
|
||||||
// 设置圆角
|
// 设置圆角
|
||||||
document.documentElement.style.setProperty('--custom-radius', `${settingStore.customRadius}rem`)
|
document.documentElement.style.setProperty(
|
||||||
|
'--custom-radius',
|
||||||
|
`${settingStore.customRadius}rem`
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 应用主题
|
// 应用主题
|
||||||
|
|||||||
@@ -48,20 +48,11 @@
|
|||||||
"setting": {
|
"setting": {
|
||||||
"menuType": {
|
"menuType": {
|
||||||
"title": "Menu Layout",
|
"title": "Menu Layout",
|
||||||
"list": [
|
"list": ["Vertical", "Horizontal", "Mixed", "Dual"]
|
||||||
"Vertical",
|
|
||||||
"Horizontal",
|
|
||||||
"Mixed",
|
|
||||||
"Dual"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"title": "Theme Style",
|
"title": "Theme Style",
|
||||||
"list": [
|
"list": ["Light", "Dark", "System"]
|
||||||
"Light",
|
|
||||||
"Dark",
|
|
||||||
"System"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"title": "Menu Style"
|
"title": "Menu Style"
|
||||||
@@ -71,17 +62,11 @@
|
|||||||
},
|
},
|
||||||
"box": {
|
"box": {
|
||||||
"title": "Box Style",
|
"title": "Box Style",
|
||||||
"list": [
|
"list": ["Border", "Shadow"]
|
||||||
"Border",
|
|
||||||
"Shadow"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"container": {
|
"container": {
|
||||||
"title": "Container Width",
|
"title": "Container Width",
|
||||||
"list": [
|
"list": ["Full", "Boxed"]
|
||||||
"Full",
|
|
||||||
"Boxed"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"basics": {
|
"basics": {
|
||||||
"title": "Basic Config",
|
"title": "Basic Config",
|
||||||
@@ -127,14 +112,8 @@
|
|||||||
"notice": {
|
"notice": {
|
||||||
"title": "Notice",
|
"title": "Notice",
|
||||||
"btnRead": "Mark as read",
|
"btnRead": "Mark as read",
|
||||||
"bar": [
|
"bar": ["Notice", "Message", "Todo"],
|
||||||
"Notice",
|
"text": ["No"],
|
||||||
"Message",
|
|
||||||
"Todo"
|
|
||||||
],
|
|
||||||
"text": [
|
|
||||||
"No"
|
|
||||||
],
|
|
||||||
"viewAll": "View all"
|
"viewAll": "View all"
|
||||||
},
|
},
|
||||||
"worktab": {
|
"worktab": {
|
||||||
|
|||||||
@@ -48,20 +48,11 @@
|
|||||||
"setting": {
|
"setting": {
|
||||||
"menuType": {
|
"menuType": {
|
||||||
"title": "菜单布局",
|
"title": "菜单布局",
|
||||||
"list": [
|
"list": ["垂直", "水平", "混合", "双列"]
|
||||||
"垂直",
|
|
||||||
"水平",
|
|
||||||
"混合",
|
|
||||||
"双列"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"title": "主题风格",
|
"title": "主题风格",
|
||||||
"list": [
|
"list": ["浅色", "深色", "系统"]
|
||||||
"浅色",
|
|
||||||
"深色",
|
|
||||||
"系统"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"menu": {
|
"menu": {
|
||||||
"title": "菜单风格"
|
"title": "菜单风格"
|
||||||
@@ -71,17 +62,11 @@
|
|||||||
},
|
},
|
||||||
"box": {
|
"box": {
|
||||||
"title": "盒子样式",
|
"title": "盒子样式",
|
||||||
"list": [
|
"list": ["边框", "阴影"]
|
||||||
"边框",
|
|
||||||
"阴影"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"container": {
|
"container": {
|
||||||
"title": "容器宽度",
|
"title": "容器宽度",
|
||||||
"list": [
|
"list": ["铺满", "定宽"]
|
||||||
"铺满",
|
|
||||||
"定宽"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"basics": {
|
"basics": {
|
||||||
"title": "基础配置",
|
"title": "基础配置",
|
||||||
@@ -127,14 +112,8 @@
|
|||||||
"notice": {
|
"notice": {
|
||||||
"title": "通知",
|
"title": "通知",
|
||||||
"btnRead": "标为已读",
|
"btnRead": "标为已读",
|
||||||
"bar": [
|
"bar": ["通知", "消息", "代办"],
|
||||||
"通知",
|
"text": ["暂无"],
|
||||||
"消息",
|
|
||||||
"代办"
|
|
||||||
],
|
|
||||||
"text": [
|
|
||||||
"暂无"
|
|
||||||
],
|
|
||||||
"viewAll": "查看全部"
|
"viewAll": "查看全部"
|
||||||
},
|
},
|
||||||
"worktab": {
|
"worktab": {
|
||||||
|
|||||||
@@ -109,7 +109,11 @@ export class MenuProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 如果有有效的 component,保留
|
// 如果有有效的 component,保留
|
||||||
if (item.component && item.component !== '' && item.component !== RoutesAlias.Layout) {
|
if (
|
||||||
|
item.component &&
|
||||||
|
item.component !== '' &&
|
||||||
|
item.component !== RoutesAlias.Layout
|
||||||
|
) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -156,7 +156,9 @@ export const useSettingStore = defineStore(
|
|||||||
* 根据当前日期和节日日期判断是否显示烟花效果
|
* 根据当前日期和节日日期判断是否显示烟花效果
|
||||||
*/
|
*/
|
||||||
const isShowFireworks = computed((): boolean => {
|
const isShowFireworks = computed((): boolean => {
|
||||||
return festivalDate.value === useCeremony().currentFestivalData.value?.date ? false : true
|
return festivalDate.value === useCeremony().currentFestivalData.value?.date
|
||||||
|
? false
|
||||||
|
: true
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -65,7 +65,9 @@ export const useWorktabStore = defineStore(
|
|||||||
const hasOpenedTabs = computed(() => opened.value.length > 0)
|
const hasOpenedTabs = computed(() => opened.value.length > 0)
|
||||||
const hasMultipleTabs = computed(() => opened.value.length > 1)
|
const hasMultipleTabs = computed(() => opened.value.length > 1)
|
||||||
const currentTabIndex = computed(() =>
|
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) {
|
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]
|
current.value = opened.value[newIndex]
|
||||||
safeRouterPush(current.value)
|
safeRouterPush(current.value)
|
||||||
}
|
}
|
||||||
@@ -415,7 +418,8 @@ export const useWorktabStore = defineStore(
|
|||||||
if (tab.fixedTab) {
|
if (tab.fixedTab) {
|
||||||
// 固定标签插入到所有固定标签的末尾
|
// 固定标签插入到所有固定标签的末尾
|
||||||
const firstNonFixedIndex = opened.value.findIndex((t) => !t.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)
|
opened.value.splice(insertIndex, 0, tab)
|
||||||
} else {
|
} else {
|
||||||
// 非固定标签插入到所有固定标签后
|
// 非固定标签插入到所有固定标签后
|
||||||
|
|||||||
Vendored
+4
-1
@@ -109,7 +109,10 @@ declare namespace Api {
|
|||||||
|
|
||||||
/** 用户搜索参数 */
|
/** 用户搜索参数 */
|
||||||
type UserSearchParams = Partial<
|
type UserSearchParams = Partial<
|
||||||
Pick<UserListItem, 'id' | 'userName' | 'userGender' | 'userPhone' | 'userEmail' | 'status'> &
|
Pick<
|
||||||
|
UserListItem,
|
||||||
|
'id' | 'userName' | 'userGender' | 'userPhone' | 'userEmail' | 'status'
|
||||||
|
> &
|
||||||
Api.Common.CommonSearchParams
|
Api.Common.CommonSearchParams
|
||||||
>
|
>
|
||||||
|
|
||||||
|
|||||||
@@ -280,9 +280,7 @@ export interface MapChartProps extends BaseChartProps {
|
|||||||
|
|
||||||
// 双向堆叠柱状图 Props 接口(人口金字塔样式)
|
// 双向堆叠柱状图 Props 接口(人口金字塔样式)
|
||||||
export interface BidirectionalBarChartProps
|
export interface BidirectionalBarChartProps
|
||||||
extends BaseChartProps,
|
extends BaseChartProps, AxisDisplayProps, InteractionProps {
|
||||||
AxisDisplayProps,
|
|
||||||
InteractionProps {
|
|
||||||
/** 正向数据(向上显示) */
|
/** 正向数据(向上显示) */
|
||||||
positiveData: number[]
|
positiveData: number[]
|
||||||
/** 负向数据(向下显示) */
|
/** 负向数据(向下显示) */
|
||||||
|
|||||||
@@ -67,7 +67,11 @@ axiosInstance.interceptors.request.use(
|
|||||||
const { accessToken } = useUserStore()
|
const { accessToken } = useUserStore()
|
||||||
if (accessToken) request.headers.set('Authorization', accessToken)
|
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.headers.set('Content-Type', 'application/json')
|
||||||
request.data = JSON.stringify(request.data)
|
request.data = JSON.stringify(request.data)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)) {
|
if (immediate && (!this.ws || this.ws.readyState !== WebSocket.OPEN)) {
|
||||||
console.error('WebSocket未连接,无法立即发送消息')
|
console.error('WebSocket未连接,无法立即发送消息')
|
||||||
|
|||||||
@@ -77,7 +77,9 @@ class StorageCompatibilityManager {
|
|||||||
const storageKeys = Object.keys(localStorage)
|
const storageKeys = Object.keys(localStorage)
|
||||||
const versionPattern = StorageConfig.createVersionPattern()
|
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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -75,7 +75,9 @@ export function registerResourceErrorHandler() {
|
|||||||
const target = event.target as HTMLElement
|
const target = event.target as HTMLElement
|
||||||
if (
|
if (
|
||||||
target &&
|
target &&
|
||||||
(target.tagName === 'IMG' || target.tagName === 'SCRIPT' || target.tagName === 'LINK')
|
(target.tagName === 'IMG' ||
|
||||||
|
target.tagName === 'SCRIPT' ||
|
||||||
|
target.tagName === 'LINK')
|
||||||
) {
|
) {
|
||||||
console.error('[ResourceError]', {
|
console.error('[ResourceError]', {
|
||||||
tagName: target.tagName,
|
tagName: target.tagName,
|
||||||
|
|||||||
@@ -97,7 +97,9 @@ class VersionManager {
|
|||||||
const oldSysKey =
|
const oldSysKey =
|
||||||
storageKeys.find(
|
storageKeys.find(
|
||||||
(key) =>
|
(key) =>
|
||||||
StorageConfig.isVersionedKey(key) && key !== currentVersionPrefix && !key.includes('-')
|
StorageConfig.isVersionedKey(key) &&
|
||||||
|
key !== currentVersionPrefix &&
|
||||||
|
!key.includes('-')
|
||||||
) || null
|
) || null
|
||||||
|
|
||||||
// 查找旧版本的分离存储键
|
// 查找旧版本的分离存储键
|
||||||
@@ -121,7 +123,9 @@ class VersionManager {
|
|||||||
return upgradeLogList.value.some((item) => {
|
return upgradeLogList.value.some((item) => {
|
||||||
const itemVersion = this.normalizeVersion(item.version)
|
const itemVersion = this.normalizeVersion(item.version)
|
||||||
return (
|
return (
|
||||||
item.requireReLogin && itemVersion > normalizedStored && itemVersion <= normalizedCurrent
|
item.requireReLogin &&
|
||||||
|
itemVersion > normalizedStored &&
|
||||||
|
itemVersion <= normalizedCurrent
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,7 +185,11 @@
|
|||||||
],
|
],
|
||||||
password: [
|
password: [
|
||||||
{ required: true, validator: validatePassword, trigger: 'blur' },
|
{ 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' }],
|
confirmPassword: [{ required: true, validator: validateConfirmPassword, trigger: 'blur' }],
|
||||||
agreement: [{ validator: validateAgreement, trigger: 'change' }]
|
agreement: [{ validator: validateAgreement, trigger: 'change' }]
|
||||||
|
|||||||
@@ -11,7 +11,9 @@
|
|||||||
<div class="ml-1">
|
<div class="ml-1">
|
||||||
<h3 class="mt-5 text-lg font-medium">用户概述</h3>
|
<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">比上周 <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>
|
||||||
<div class="flex-b mt-2">
|
<div class="flex-b mt-2">
|
||||||
<div class="flex-1" v-for="(item, index) in list" :key="index">
|
<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">
|
<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">
|
<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>
|
<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">
|
<div class="flex-c mt-1">
|
||||||
<span class="text-xs text-g-600">较上周</span>
|
<span class="text-xs text-g-600">较上周</span>
|
||||||
<span
|
<span
|
||||||
|
|||||||
@@ -33,7 +33,9 @@
|
|||||||
<ElTableColumn label="性别" prop="avatar">
|
<ElTableColumn label="性别" prop="avatar">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div style="display: flex; align-items: center">
|
<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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ElTableColumn>
|
</ElTableColumn>
|
||||||
|
|||||||
@@ -170,7 +170,12 @@
|
|||||||
if (form.menuType === 'menu') {
|
if (form.menuType === 'menu') {
|
||||||
return [
|
return [
|
||||||
...baseItems,
|
...baseItems,
|
||||||
{ label: '菜单名称', key: 'name', type: 'input', props: { placeholder: '菜单名称' } },
|
{
|
||||||
|
label: '菜单名称',
|
||||||
|
key: 'name',
|
||||||
|
type: 'input',
|
||||||
|
props: { placeholder: '菜单名称' }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: createLabelTooltip(
|
label: createLabelTooltip(
|
||||||
'路由地址',
|
'路由地址',
|
||||||
@@ -180,7 +185,12 @@
|
|||||||
type: 'input',
|
type: 'input',
|
||||||
props: { placeholder: '如:/dashboard 或 console' }
|
props: { placeholder: '如:/dashboard 或 console' }
|
||||||
},
|
},
|
||||||
{ label: '权限标识', key: 'label', type: 'input', props: { placeholder: '如:User' } },
|
{
|
||||||
|
label: '权限标识',
|
||||||
|
key: 'label',
|
||||||
|
type: 'input',
|
||||||
|
props: { placeholder: '如:User' }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: createLabelTooltip(
|
label: createLabelTooltip(
|
||||||
'组件路径',
|
'组件路径',
|
||||||
@@ -190,7 +200,12 @@
|
|||||||
type: 'input',
|
type: 'input',
|
||||||
props: { placeholder: '如:/system/user 或留空' }
|
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(
|
label: createLabelTooltip(
|
||||||
'角色权限',
|
'角色权限',
|
||||||
|
|||||||
@@ -31,7 +31,9 @@
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<ElButton @click="outputSelectedData" style="margin-left: 8px">获取选中数据</ElButton>
|
<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">{{
|
<ElButton @click="toggleSelectAll" style="margin-left: 8px">{{
|
||||||
isSelectAll ? '取消全选' : '全部选择'
|
isSelectAll ? '取消全选' : '全部选择'
|
||||||
}}</ElButton>
|
}}</ElButton>
|
||||||
@@ -114,7 +116,9 @@
|
|||||||
checked: auth.checked || false
|
checked: auth.checked || false
|
||||||
}))
|
}))
|
||||||
|
|
||||||
processed.children = processed.children ? [...processed.children, ...authNodes] : authNodes
|
processed.children = processed.children
|
||||||
|
? [...processed.children, ...authNodes]
|
||||||
|
: authNodes
|
||||||
}
|
}
|
||||||
|
|
||||||
// 递归处理子节点
|
// 递归处理子节点
|
||||||
|
|||||||
@@ -4,7 +4,10 @@
|
|||||||
<div class="relative flex-b mt-2.5 max-md:block max-md:mt-1">
|
<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="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">
|
<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
|
<img
|
||||||
class="relative z-10 w-20 h-20 mt-30 mx-auto object-cover border-2 border-white rounded-full"
|
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"
|
src="@imgs/user/avatar.webp"
|
||||||
@@ -62,7 +65,11 @@
|
|||||||
<ElInput v-model="form.realName" :disabled="!isEdit" />
|
<ElInput v-model="form.realName" :disabled="!isEdit" />
|
||||||
</ElFormItem>
|
</ElFormItem>
|
||||||
<ElFormItem label="性别" prop="sex" class="ml-5">
|
<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
|
<ElOption
|
||||||
v-for="item in options"
|
v-for="item in options"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
@@ -92,7 +99,12 @@
|
|||||||
</ElRow>
|
</ElRow>
|
||||||
|
|
||||||
<ElFormItem label="个人介绍" prop="des" class="h-32">
|
<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>
|
</ElFormItem>
|
||||||
|
|
||||||
<div class="flex-c justify-end [&_.el-button]:!w-27.5">
|
<div class="flex-c justify-end [&_.el-button]:!w-27.5">
|
||||||
@@ -106,7 +118,12 @@
|
|||||||
<div class="art-card-sm my-5">
|
<div class="art-card-sm my-5">
|
||||||
<h1 class="p-4 text-xl font-normal border-b border-g-300">更改密码</h1>
|
<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">
|
<ElFormItem label="当前密码" prop="password">
|
||||||
<ElInput
|
<ElInput
|
||||||
v-model="pwdForm.password"
|
v-model="pwdForm.password"
|
||||||
|
|||||||
@@ -6,11 +6,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="user-page art-full-height">
|
<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">
|
<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>
|
<template #left>
|
||||||
<ElSpace wrap>
|
<ElSpace wrap>
|
||||||
<ElButton @click="showDialog('add')" v-ripple>新增用户</ElButton>
|
<ElButton @click="showDialog('add')" v-ripple>新增用户</ElButton>
|
||||||
|
|||||||
Reference in New Issue
Block a user