Просмотр исходного кода

Merge remote-tracking branch 'remotes/origin/i18n' into v2

夜鹰 9 месяцев назад
Родитель
Сommit
c1eee04eef

+ 74 - 0
Web/src/components/CardPro/README.md

@@ -0,0 +1,74 @@
+# `CardPro` 说明
+
+`CardPro`卡片组件,基于`ElCard`二次封装,增加了大小、标题、图标等属性,可以直接写属性值实现,无需像官方版本一样通过`solt`实现。组件继承了官方版的所有属性、插槽,你也可以按官方版本一样的使用。
+
+---
+
+## 如何使用
+
+### 基本用法
+
+卡片标题、内容以及图标,通过 `prefix-icon` 属性设置卡片图标。与官方版本不同的时,`CardPro` 通过 `title`
+来设置标题,如果你使用 `header` 来设置,它将覆盖 `CardPro` 组件的特有属性,与官方版本完全一致。
+
+```html
+<template>
+    <card-pro title="标题" prefix-icon="ele-DocumentCopy">
+        <!-- 你的内容 -->
+    </card-pro>
+</template>
+<script lang="ts" setup>
+import CardPro from '/@/components/CardPro/index.vue';
+</script>
+```
+
+### 后缀
+
+通过 `suffix` 属性设置卡片右上角文本,你也可以通过 `solt` 来实现自定义内容
+
+```html
+<template>
+    <card-pro title="标题" suffix="more >>>">
+        <!-- 你的内容 -->
+    </card-pro>
+</template>
+<script lang="ts" setup>
+import CardPro from '/@/components/CardPro/index.vue';
+</script>
+```
+
+使用  `solt` 方式来自定义后缀内容,你可以在其中添加按钮、链接、图标、输入框等
+
+```html
+<template>
+    <card-pro title="标题">
+        <template #suffix>
+           <el-button type="primary" icon="ele-Refresh" round plain @click="reset">刷新</el-button>
+        </template>
+
+        <!-- 你的内容 -->
+    </card-pro>
+</template>
+<script lang="ts" setup>
+import CardPro from '/@/components/CardPro/index.vue';
+</script>
+```
+
+### 不同尺寸
+
+使用 `size` 属性配置尺寸,可使用 `large`、`default` 和 `small`
+
+```html
+<template>
+    <card-pro title="标题" size="small">
+        <!-- 你的内容 -->
+    </card-pro>
+</template>
+<script lang="ts" setup>
+import CardPro from '/@/components/CardPro/index.vue';
+</script>
+```
+
+---
+
+最新更新于 2025.10.10

+ 89 - 0
Web/src/components/CardPro/index.vue

@@ -0,0 +1,89 @@
+<template>
+    <el-card v-bind="$attrs" :class="sizeClass">
+        <template #header v-if="title || $slots.prefix || prefixIcon || $slots.suffix || suffix">
+            <div class="cardpro-header">
+                <div v-if="$slots.prefix || prefixIcon" class="cardpro-header-prefix">
+                    <slot v-if="$slots.prefix" name="prefix" />
+                    <el-icon v-else-if="prefixIcon">
+                        <component :is="prefixIcon" />
+                    </el-icon>
+                </div>
+                <div class="cardpro-header-title">
+                    <span>{{ props.title }}</span>
+                </div>
+                <div v-if="$slots.suffix || suffix" class="cardpro-header-suffix">
+                    <slot v-if="$slots.suffix" name="suffix" />
+                    <span v-else-if="props.suffix">{{ props.suffix }}</span>
+                </div>
+            </div>
+        </template>
+        
+        <template v-for="(value, name) in $slots" #[name]="scopedData">
+            <slot :name="name" v-bind="scopedData"></slot>
+        </template>
+    </el-card>
+</template>
+
+<script setup lang="ts" name="CardPro">
+import { computed } from 'vue';
+import { storeToRefs } from 'pinia';
+import { useSizeProp } from 'element-plus'
+import { useThemeConfig } from '/@/stores/themeConfig';
+
+const storesThemeConfig = useThemeConfig();
+const { themeConfig } = storeToRefs(storesThemeConfig);
+
+const props = defineProps({
+    title: { type: String },
+    prefixIcon: { type: String },
+    suffix: { type: String },
+    size: useSizeProp
+});
+
+// 获取全局组件大小
+const getGlobalComponentSize = computed(() => {
+	return themeConfig.value.globalComponentSize;
+});
+
+const sizeClass = computed(() => {
+    if(props.size) return  "cardpro-" + props.size;
+    else return "cardpro-" + getGlobalComponentSize.value;
+});
+</script>
+
+<style lang="scss" scoped>
+.cardpro-header {
+    display: flex;
+    align-items: center;
+
+    &-prefix {
+        height: 100%;
+        margin-right: 5px;
+        display: flex;
+        align-items: center;
+        margin-top: 1px;
+    }
+    &-title { flex: 1; padding: var(--cardpro-padding) 0; }
+    &-suffix {
+        height: 100%;
+        margin-left: 5px;
+    }
+}
+
+.cardpro-small {
+    --cardpro-padding: 10px
+}
+.cardpro-default {
+    --cardpro-padding: 15px
+}
+.cardpro-large {
+    --cardpro-padding: 20px
+}
+
+:deep(.el-card__header) {
+    padding: 0 var(--cardpro-padding);
+}
+:deep(.el-card__body) {
+    padding: var(--cardpro-padding);
+}
+</style>

+ 8 - 0
Web/src/layout/navBars/topBar/user.vue

@@ -93,6 +93,7 @@
 
 <script setup lang="ts" name="layoutBreadcrumbUser">
 import { defineAsyncComponent, ref, computed, reactive, onMounted } from 'vue';
+import { useCssVar } from '@vueuse/core'
 import { useRouter } from 'vue-router';
 import { ElMessageBox, ElMessage, ElNotification } from 'element-plus';
 import screenfull from 'screenfull';
@@ -245,6 +246,13 @@ const onLanguageChange = async (lang: string) => {
 // 初始化组件大小/i18n
 const initI18nOrSize = (value: string, attr: string) => {
 	(<any>state)[attr] = Local.get('themeConfig')[value];
+
+    // 设置菜单高度-横向
+    useCssVar('--el-menu-horizontal-height').value = 'var(--el-menu-item-height-' + themeConfig.value.globalComponentSize + ')';
+    useCssVar('--el-menu-horizontal-sub-item-height').value = 'var(--el-menu-item-height-' + themeConfig.value.globalComponentSize + ')';
+    // 设置菜单高度-纵向
+    useCssVar('--el-menu-item-height').value = 'var(--el-menu-item-height-' + themeConfig.value.globalComponentSize + ')';
+    useCssVar('--el-menu-sub-item-height').value = 'var(--el-menu-item-height-' + themeConfig.value.globalComponentSize + ')';
 };
 // 页面加载时
 onMounted(async () => {

+ 5 - 0
Web/src/theme/app.scss

@@ -26,6 +26,11 @@
 	//--el-text-color-primary: rgba(0, 0, 0, .75) !important;
 	--el-text-color-regular: rgba(0, 0, 0, 0.99) !important;
 	//--el-text-color-secondary: var(--el-text-color-regular) !important;
+
+    // 导航菜单项高度,建议设置为偶数,否则图标与文本会在垂直位置上存在1px差
+    --el-menu-item-height-small: 34px;
+    --el-menu-item-height-default: 44px;
+    --el-menu-item-height-large: 50px;
 }
 
 html,

+ 9 - 8
Web/src/theme/element.scss

@@ -1,4 +1,4 @@
-@import 'mixins/index.scss';
+@use 'mixins/index.scss' as *;
 
 /* Button 按钮
 ------------------------------- */
@@ -119,8 +119,8 @@
 .el-menu-item,
 .el-sub-menu__title {
 	color: var(--next-bg-menuBarColor);
-	height: 44px !important;
-	line-height: 44px !important;
+	// height: 44px !important;
+	// line-height: 44px !important;
 }
 
 // 修复点击左侧菜单折叠再展开时,宽度不跟随问题
@@ -212,13 +212,13 @@
 
 		.el-menu-item,
 		.el-sub-menu {
-			height: 45px !important;
-			line-height: 45px !important;
+			// height: 45px !important;
+			// line-height: 45px !important;
 			color: var(--next-bg-topBarColor);
 
 			.el-sub-menu__title {
-				height: 45px !important;
-				line-height: 45px !important;
+				// height: 45px !important;
+				// line-height: 45px !important;
 				color: var(--next-bg-topBarColor);
 			}
 
@@ -256,7 +256,8 @@
 
 	.el-menu-item,
 	.el-sub-menu__title {
-		height: 50px !important;
+		height: 50px;
+        line-height: 50px;
 		color: var(--next-bg-topBarColor);
 	}
 

+ 10 - 10
Web/src/theme/index.scss

@@ -1,10 +1,10 @@
-@import 'element-plus/dist/index.css';
-@import './app.scss';
-@import 'common/transition.scss';
-@import './other.scss';
-@import './element.scss';
-@import './media/media.scss';
-@import './waves.scss';
-@import './dark.scss';
-@import './iconfont/font_2298093_rnp72ifj3ba.scss';
-@import './font-awesome/font-awesome.scss';
+@use 'element-plus/dist/index.css' as *;
+@use './app.scss' as *;
+@use 'common/transition.scss' as *;
+@use './other.scss' as *;
+@use './element.scss' as *;
+@use './media/media.scss' as *;
+@use './waves.scss' as *;
+@use './dark.scss' as *;
+@use './iconfont/font_2298093_rnp72ifj3ba.scss' as *;
+@use './font-awesome/font-awesome.scss' as *;

+ 1 - 1
Web/src/theme/media/chart.scss

@@ -1,4 +1,4 @@
-@import './index.scss';
+@use './index.scss' as *;
 
 /* 页面宽度小于768px
 ------------------------------- */

+ 1 - 1
Web/src/theme/media/cityLinkage.scss

@@ -1,4 +1,4 @@
-@import './index.scss';
+@use './index.scss' as *;
 
 /* 页面宽度小于576px
 ------------------------------- */

+ 1 - 1
Web/src/theme/media/date.scss

@@ -1,4 +1,4 @@
-@import './index.scss';
+@use './index.scss' as *;
 
 /* 页面宽度小于768px
 ------------------------------- */

+ 1 - 1
Web/src/theme/media/dialog.scss

@@ -1,4 +1,4 @@
-@import './index.scss';
+@use './index.scss' as *;
 
 /* 页面宽度小于800px
 ------------------------------- */

+ 1 - 1
Web/src/theme/media/error.scss

@@ -1,4 +1,4 @@
-@import './index.scss';
+@use './index.scss' as *;
 
 /* 页面宽度小于768px
 ------------------------------- */

+ 1 - 1
Web/src/theme/media/form.scss

@@ -1,4 +1,4 @@
-@import './index.scss';
+@use './index.scss' as *;
 
 /* 页面宽度小于576px
 ------------------------------- */

+ 1 - 1
Web/src/theme/media/home.scss

@@ -1,4 +1,4 @@
-@import './index.scss';
+@use './index.scss' as *;
 
 /* 页面宽度小于768px
 ------------------------------- */

+ 1 - 1
Web/src/theme/media/layout.scss

@@ -1,4 +1,4 @@
-@import './index.scss';
+@use './index.scss' as *;
 
 /* 页面宽度小于576px
 ------------------------------- */

+ 1 - 1
Web/src/theme/media/login.scss

@@ -1,4 +1,4 @@
-@import './index.scss';
+@use './index.scss' as *;
 
 /* 页面宽度小于1200px
 ------------------------------- */

+ 13 - 13
Web/src/theme/media/media.scss

@@ -1,13 +1,13 @@
-@import './login.scss';
-@import './error.scss';
-@import './layout.scss';
-@import './personal.scss';
-@import './tagsView.scss';
-@import './home.scss';
-@import './chart.scss';
-@import './form.scss';
-@import './scrollbar.scss';
-@import './pagination.scss';
-@import './dialog.scss';
-@import './cityLinkage.scss';
-@import './date.scss';
+@use './login.scss' as *;
+@use './error.scss' as *;
+@use './layout.scss' as *;
+@use './personal.scss' as *;
+@use './tagsView.scss' as *;
+@use './home.scss' as *;
+@use './chart.scss' as *;
+@use './form.scss' as *;
+@use './scrollbar.scss' as *;
+@use './pagination.scss' as *;
+@use './dialog.scss' as *;
+@use './cityLinkage.scss' as *;
+@use './date.scss' as *;

+ 1 - 1
Web/src/theme/media/pagination.scss

@@ -1,4 +1,4 @@
-@import './index.scss';
+@use './index.scss' as *;
 
 /* 页面宽度小于576px
 ------------------------------- */

+ 1 - 1
Web/src/theme/media/personal.scss

@@ -1,4 +1,4 @@
-@import './index.scss';
+@use './index.scss' as *;
 
 /* 页面宽度小于768px
 ------------------------------- */

+ 1 - 1
Web/src/theme/media/scrollbar.scss

@@ -1,4 +1,4 @@
-@import './index.scss';
+@use './index.scss' as *;
 
 /* 页面宽度小于768px
 ------------------------------- */

+ 1 - 1
Web/src/theme/media/tagsView.scss

@@ -1,4 +1,4 @@
-@import './index.scss';
+@use './index.scss' as *;
 
 /* 页面宽度小于768px
 ------------------------------- */

+ 6 - 8
Web/src/views/home/widgets/components/about.vue

@@ -1,10 +1,6 @@
 <template>
-	<el-card shadow="hover" header="关于项目" class="item-background">
-		<template #header>
-			<el-icon style="display: inline; vertical-align: middle"> <ele-QuestionFilled /> </el-icon>
-			<span> 关于项目 </span>
-		</template>
-		<p>
+	<card-pro title="关于项目" prefix-icon="ele-QuestionFilled" shadow="hover">
+        <p>
 			基于 .NET6 (Furion/SqlSugar) 实现的通用权限开发框架,前端采用
 			Vue3+Element-plus+Vite5,整合众多优秀技术和框架,模块插件式开发。集成多租户、缓存、数据校验、鉴权、事件总线、动态API、通讯、远程请求、任务调度、打印等众多黑科技。代码结构简单清晰,注释详尽,易于上手与二次开发,即便是复杂业务逻辑也能迅速实现,真正实现“开箱即用”。
 		</p>
@@ -15,7 +11,7 @@
 				<img src="https://img.shields.io/badge/license-MIT-yellow" alt="license" style="vertical-align: middle" />
 			</a>
 		</p>
-	</el-card>
+    </card-pro>
 </template>
 
 <script lang="ts">
@@ -25,7 +21,9 @@ export default {
 	description: '点个星星支持一下',
 };
 </script>
-<script setup lang="ts" name="about"></script>
+<script setup lang="ts" name="about">
+import CardPro from '/@/components/CardPro/index.vue';
+</script>
 
 <style scoped>
 .item-background p {

+ 7 - 8
Web/src/views/home/widgets/components/commit.vue

@@ -1,11 +1,6 @@
 <template>
-	<el-card shadow="hover" header="更新记录">
-		<template #header>
-			<el-icon style="display: inline; vertical-align: middle"> <ele-DocumentCopy /> </el-icon>
-			<span> 更新记录 </span>
-			<el-button type="primary" icon="ele-Refresh" round plain @click="refresh" style="float: right">更新记录</el-button>
-		</template>
-		<div class="commit" v-loading="state.loading">
+	<card-pro title="更新记录" prefix-icon="ele-DocumentCopy" shadow="hover">
+        <div class="commit" v-loading="state.loading">
 			<el-timeline style="max-width: 600px" v-if="state.list.length > 0">
 				<el-timeline-item v-for="(item, index) in state.list" :key="index" :timestamp="formatDate(new Date(item.commit.committer.date), 'YYYY-mm-dd HH:MM:SS')">
 					<el-link style="white-space: pre-line; word-break: break-all" :href="item.html_url" target="_blank"> {{ item.commit.message }}</el-link>
@@ -13,7 +8,10 @@
 			</el-timeline>
 			<el-empty v-else description="空"></el-empty>
 		</div>
-	</el-card>
+        <template #suffix>
+            <el-button type="primary" icon="ele-Refresh" round plain @click="refresh">更新记录</el-button>
+        </template>
+    </card-pro>
 </template>
 
 <script lang="ts">
@@ -27,6 +25,7 @@ export default {
 <script setup lang="ts" name="commit">
 import { reactive, onMounted } from 'vue';
 import { formatDate } from '/@/utils/formatTime';
+import CardPro from '/@/components/CardPro/index.vue';
 
 const state = reactive({
 	loading: false,

+ 4 - 5
Web/src/views/home/widgets/components/schedule.vue

@@ -1,8 +1,6 @@
 <template>
-	<el-card shadow="hover" header="我的日程" class="item-background">
-		<template #header>
-			<el-icon style="display: inline; vertical-align: middle"> <ele-Calendar /> </el-icon>
-			<span> 我的日程 </span>
+	<card-pro shadow="hover" title="我的日程" prefix-icon="ele-Calendar" class="item-background">
+		<template #suffix>
 			<el-button type="primary" icon="ele-CirclePlus" round plain @click="openAddSchedule" style="float: right">添加日程</el-button>
 		</template>
 
@@ -47,7 +45,7 @@
 		</div>
 
 		<EditSchedule ref="editScheduleRef" :title="state.editTitle" @handleQuery="handleQuery"> </EditSchedule>
-	</el-card>
+	</card-pro>
 </template>
 
 <script lang="ts">
@@ -64,6 +62,7 @@ import { dayjs, ElMessage, ElMessageBox } from 'element-plus';
 import type { CalendarDateType, CalendarInstance } from 'element-plus';
 import calendarCom from '/@/utils/calendar.js';
 
+import CardPro from '/@/components/CardPro/index.vue';
 import EditSchedule from '/@/views/home/widgets/components/scheduleEdit.vue';
 
 import { getAPI } from '/@/utils/axios-utils';

+ 3 - 2
Web/src/views/system/infoSetting/index.vue

@@ -1,6 +1,6 @@
 <template>
 	<div>
-		<el-card shadow="hover" v-loading="state.isLoading" style="height: 100%;">
+		<CardPro shadow="hover" v-loading="state.isLoading" style="height: 100%;">
 			<el-descriptions title="系统信息配置" :column="2" :border="true">
 				<template #title>
 					<el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"> <ele-Setting /> </el-icon> 系统信息配置
@@ -50,7 +50,7 @@
 					<el-button type="primary" icon="ele-SuccessFilled" @click="onSave">保存</el-button>
 				</template>
 			</el-descriptions>
-		</el-card>
+		</CardPro>
 	</div>
 </template>
 
@@ -62,6 +62,7 @@ import { fileToBase64 } from '/@/utils/base64Conver';
 import { getAPI } from '/@/utils/axios-utils';
 import { SysConfigApi } from '/@/api-services';
 import GSysDict from '/@/components/sysDict/sysDict.vue';
+import CardPro from '/@/components/CardPro/index.vue';
 
 const uploadRef = ref<UploadInstance>();
 const state = reactive({