Compare commits
2 Commits
1cc427cbb0
...
2f5ee49594
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f5ee49594 | |||
| a0afedf5f3 |
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"tabWidth": 4,
|
||||
"useTabs": true,
|
||||
"semi": false,
|
||||
"vueIndentScriptAndStyle": true,
|
||||
"singleQuote": true,
|
||||
|
||||
+3
-13
@@ -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}": [
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}`
|
||||
})
|
||||
|
||||
|
||||
@@ -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
@@ -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: [
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
/**
|
||||
* 获取所有列配置
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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`
|
||||
)
|
||||
}
|
||||
|
||||
// 应用主题
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
/**
|
||||
|
||||
@@ -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 {
|
||||
// 非固定标签插入到所有固定标签后
|
||||
|
||||
Vendored
+4
-1
@@ -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
|
||||
>
|
||||
|
||||
|
||||
@@ -280,9 +280,7 @@ export interface MapChartProps extends BaseChartProps {
|
||||
|
||||
// 双向堆叠柱状图 Props 接口(人口金字塔样式)
|
||||
export interface BidirectionalBarChartProps
|
||||
extends BaseChartProps,
|
||||
AxisDisplayProps,
|
||||
InteractionProps {
|
||||
extends BaseChartProps, AxisDisplayProps, InteractionProps {
|
||||
/** 正向数据(向上显示) */
|
||||
positiveData: number[]
|
||||
/** 负向数据(向下显示) */
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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未连接,无法立即发送消息')
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
// 递归处理子节点
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user