mirror of
https://gitee.com/JavaLionLi/plus-ui.git
synced 2026-05-14 19:28:02 +00:00
Compare commits
24 Commits
future/6.X
...
v5.6.1-v2.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fd2b6f137 | ||
|
|
2a6c440593 | ||
|
|
0ddf391de3 | ||
|
|
f599aba890 | ||
|
|
38a15e6cd3 | ||
|
|
e77fe0e618 | ||
|
|
493d1131bf | ||
|
|
f37af6f48c | ||
|
|
a79f2fb6c8 | ||
|
|
eb6827765c | ||
|
|
6bc3c618fe | ||
|
|
0076f5f6f7 | ||
|
|
ef5ea98a03 | ||
|
|
73f2374c72 | ||
|
|
9dcb392220 | ||
|
|
54636ac14f | ||
|
|
51a852caea | ||
|
|
5c9c940588 | ||
|
|
dfd1dc29d1 | ||
|
|
b411505b19 | ||
|
|
52ea8895d6 | ||
|
|
1b46739799 | ||
|
|
b000788785 | ||
|
|
2dc094c1db |
@@ -1,7 +1,7 @@
|
||||
## 平台简介
|
||||
|
||||
- 本仓库为前端技术栈 [Vue3](https://v3.cn.vuejs.org) + [TS](https://www.typescriptlang.org/) + [Element Plus](https://element-plus.org/zh-CN) + [Vite](https://cn.vitejs.dev) 版本。
|
||||
- 成员项目: 基于 vben5(ant-design-vue) 的前端项目 [ruoyi-plus-vben5](https://gitee.com/dapppp/ruoyi-plus-vben5)
|
||||
- 成员项目: 基于 vben5(ant-design-vue) 的前端项目 [ruoyi-plus-vben5](https://github.com/imdap/ruoyi-plus-vben5)
|
||||
- 成员项目: 基于soybean 的前端项目 [ruoyi-plus-soybean](https://gitee.com/xlsea/ruoyi-plus-soybean)
|
||||
|
||||
## 配套后端代码仓库地址
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package",
|
||||
"name": "ruoyi-vue-plus",
|
||||
"version": "5.5.3-2.5.3",
|
||||
"version": "5.6.1-2.6.1",
|
||||
"description": "RuoYi-Vue-Plus多租户管理系统",
|
||||
"author": "LionLi",
|
||||
"license": "MIT",
|
||||
@@ -73,8 +73,7 @@
|
||||
"unplugin-icons": "23.0.1",
|
||||
"unplugin-vue-components": "31.0.0",
|
||||
"unplugin-vue-setup-extend-plus": "1.0.1",
|
||||
"vite": "7.3.1",
|
||||
"vite-plugin-compression": "0.5.1",
|
||||
"vite": "7.3.2",
|
||||
"vite-plugin-svg-icons-ng": "^1.5.2",
|
||||
"vite-plugin-vue-devtools": "8.0.7",
|
||||
"vitest": "4.0.18",
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
|
||||
.el-dialog {
|
||||
margin: 0 auto !important;
|
||||
border-radius: var(--app-radius-lg);
|
||||
border-radius: var(--app-radius-base);
|
||||
box-shadow: var(--app-shadow-md);
|
||||
overflow: hidden;
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
}
|
||||
|
||||
.el-message-box {
|
||||
border-radius: var(--app-radius-lg);
|
||||
border-radius: var(--app-radius-base);
|
||||
box-shadow: var(--app-shadow-md);
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@
|
||||
|
||||
// Buttons
|
||||
.el-button {
|
||||
border-radius: var(--app-radius-md);
|
||||
border-radius: var(--el-border-radius-base);
|
||||
transition: transform 0.15s ease, box-shadow 0.2s ease, background-color 0.2s ease, border-color 0.2s ease;
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@
|
||||
}
|
||||
|
||||
.el-drawer {
|
||||
border-radius: var(--app-radius-lg);
|
||||
border-radius: var(--app-radius-base);
|
||||
box-shadow: var(--app-shadow-md);
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@
|
||||
|
||||
// Table polish
|
||||
.el-table {
|
||||
border-radius: var(--app-radius-lg);
|
||||
border-radius: var(--app-radius-base);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--app-shadow-sm);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
|
||||
@@ -115,7 +115,7 @@ div:focus {
|
||||
}
|
||||
|
||||
aside {
|
||||
background: #eef1f6;
|
||||
background: var(--el-fill-color-light);
|
||||
padding: 8px 24px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: var(--app-radius-md);
|
||||
@@ -131,44 +131,43 @@ aside {
|
||||
'Hiragino Sans GB',
|
||||
'Microsoft YaHei',
|
||||
sans-serif;
|
||||
color: #2c3e50;
|
||||
color: var(--el-text-color-primary);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
|
||||
a {
|
||||
color: #337ab7;
|
||||
color: var(--el-color-primary);
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: rgb(32, 160, 255);
|
||||
color: var(--el-color-primary-light-3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//main-container全局样式
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
background: var(--app-surface-bg);
|
||||
border: 1px solid var(--app-surface-border);
|
||||
border-radius: var(--app-radius-lg);
|
||||
box-shadow: var(--app-shadow-sm);
|
||||
padding: 16px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
// search面板样式
|
||||
.panel,
|
||||
.search {
|
||||
margin-bottom: 0.75rem;
|
||||
border-radius: var(--app-radius-lg);
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
background-color: var(--el-bg-color-overlay);
|
||||
padding: 0.75rem;
|
||||
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
|
||||
transition: box-shadow 0.2s ease, transform 0.2s ease, border-color 0.2s ease;
|
||||
margin-bottom: 12px;
|
||||
border-radius: var(--app-radius-base);
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
padding: 16px;
|
||||
background: var(--app-surface-bg);
|
||||
box-shadow: var(--app-shadow-sm);
|
||||
transition: box-shadow 0.2s ease, border-color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
box-shadow: var(--app-shadow-sm);
|
||||
border-color: var(--el-border-color);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: var(--app-shadow-md);
|
||||
border-color: var(--el-border-color-light);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ h6 {
|
||||
font-weight: 600;
|
||||
}
|
||||
.el-dialog:not(.is-fullscreen) {
|
||||
margin-top: 6vh !important;
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
|
||||
.el-dialog.scrollbar .el-dialog__body {
|
||||
@@ -125,8 +125,8 @@ h6 {
|
||||
/* tree border */
|
||||
.tree-border {
|
||||
margin-top: 5px;
|
||||
border: 1px solid #e5e6e7;
|
||||
background: #ffffff none;
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
background: var(--el-bg-color);
|
||||
border-radius: var(--app-radius-md);
|
||||
width: 100%;
|
||||
}
|
||||
@@ -203,12 +203,11 @@ h6 {
|
||||
box-shadow: var(--app-shadow-sm);
|
||||
border-color: var(--el-border-color-lighter);
|
||||
overflow: hidden;
|
||||
transition: box-shadow 0.2s ease, transform 0.2s ease;
|
||||
transition: box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
.el-card:hover {
|
||||
box-shadow: var(--app-shadow-md);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.card-box {
|
||||
|
||||
@@ -27,9 +27,11 @@
|
||||
--tags-view-active-border-color: var(--el-color-primary);
|
||||
|
||||
// Modern rounded style + soft shadows
|
||||
--app-radius-sm: 6px;
|
||||
--app-radius-md: 10px;
|
||||
--app-radius-lg: 14px;
|
||||
--app-radius-base: 8px;
|
||||
--app-radius-sm: calc(var(--app-radius-base) * 0.6);
|
||||
--app-radius-md: var(--app-radius-base);
|
||||
--app-radius-lg: calc(var(--app-radius-base) * 1.4);
|
||||
--app-radius-lg: var(--app-radius-base);
|
||||
--app-shadow-sm: 0 1px 2px rgba(15, 23, 42, 0.08), 0 6px 16px rgba(15, 23, 42, 0.08);
|
||||
--app-shadow-md: 0 8px 24px rgba(15, 23, 42, 0.12);
|
||||
--app-shadow-lg: 0 12px 32px rgba(15, 23, 42, 0.16);
|
||||
@@ -52,7 +54,7 @@ html.dark {
|
||||
--menuHover: #171819;
|
||||
|
||||
--subMenuBg: #1d1e1f;
|
||||
--subMenuActiveText: #1d1e1f;
|
||||
--subMenuActiveText: #f4f4f5;
|
||||
--subMenuHover: #171819;
|
||||
--subMenuTitleHover: #171819;
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-upload
|
||||
v-if="type"
|
||||
action=""
|
||||
v-if="type === 'url'"
|
||||
:action="upload.url"
|
||||
:before-upload="handleBeforeUpload"
|
||||
:http-request="handleUploadRequest"
|
||||
:on-success="handleUploadSuccess"
|
||||
:on-error="handleUploadError"
|
||||
class="editor-img-uploader"
|
||||
name="file"
|
||||
:show-file-list="false"
|
||||
:headers="upload.headers"
|
||||
>
|
||||
<i ref="uploadRef"></i>
|
||||
</el-upload>
|
||||
@@ -29,7 +31,7 @@ import '@vueup/vue-quill/dist/vue-quill.snow.css';
|
||||
|
||||
import { QuillEditor, Quill } from '@vueup/vue-quill';
|
||||
import { propTypes } from '@/utils/propTypes';
|
||||
import type { UploadRequestHandler, UploadRequestOptions } from 'element-plus';
|
||||
import { globalHeaders } from '@/utils/request';
|
||||
|
||||
defineEmits(['update:modelValue']);
|
||||
|
||||
@@ -45,11 +47,15 @@ const props = defineProps({
|
||||
/* 上传文件大小限制(MB) */
|
||||
fileSize: propTypes.number.def(5),
|
||||
/* 类型(base64格式、url格式) */
|
||||
type: propTypes.string.def('base64')
|
||||
type: propTypes.string.def('url')
|
||||
});
|
||||
|
||||
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
|
||||
|
||||
const upload = reactive<UploadOption>({
|
||||
headers: globalHeaders(),
|
||||
url: import.meta.env.VITE_APP_BASE_API + '/resource/oss/upload'
|
||||
});
|
||||
const quillEditorRef = ref();
|
||||
const uploadRef = ref<HTMLDivElement>();
|
||||
|
||||
@@ -110,9 +116,28 @@ watch(
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// 图片上传成功返回图片地址
|
||||
const handleUploadSuccess = (res: any) => {
|
||||
// 如果上传成功
|
||||
if (res.code === 200) {
|
||||
// 获取富文本实例
|
||||
const quill = toRaw(quillEditorRef.value).getQuill();
|
||||
// 获取光标位置
|
||||
const length = quill.selection.savedRange.index;
|
||||
// 插入图片,res为服务器返回的图片链接地址
|
||||
quill.insertEmbed(length, 'image', res.data.url);
|
||||
// 调整光标到最后
|
||||
quill.setSelection(length + 1);
|
||||
proxy?.$modal.closeLoading();
|
||||
} else {
|
||||
proxy?.$modal.msgError('图片插入失败');
|
||||
proxy?.$modal.closeLoading();
|
||||
}
|
||||
};
|
||||
|
||||
// 图片上传前拦截
|
||||
const handleBeforeUpload = (file: any) => {
|
||||
const type = ['image/jpeg', 'image/jpg', 'image/png', 'image/svg', 'image/svg+xml'];
|
||||
const type = ['image/jpeg', 'image/jpg', 'image/png', 'image/svg'];
|
||||
const isJPG = type.includes(file.type);
|
||||
//检验文件格式
|
||||
if (!isJPG) {
|
||||
@@ -131,41 +156,9 @@ const handleBeforeUpload = (file: any) => {
|
||||
return true;
|
||||
};
|
||||
|
||||
// base64 模式插入图片
|
||||
const handleUploadRequest: UploadRequestHandler = (options: UploadRequestOptions) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const file = options.file as File;
|
||||
const quill = toRaw(quillEditorRef.value)?.getQuill();
|
||||
if (!quill) {
|
||||
proxy?.$modal.msgError('编辑器未就绪');
|
||||
proxy?.$modal.closeLoading();
|
||||
reject(new Error('editor not ready'));
|
||||
return;
|
||||
}
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const base64 = reader.result as string;
|
||||
const range = quill.selection?.savedRange;
|
||||
const length = range ? range.index : quill.getLength();
|
||||
quill.insertEmbed(length, 'image', base64);
|
||||
quill.setSelection(length + 1);
|
||||
proxy?.$modal.closeLoading();
|
||||
options.onSuccess?.({ url: base64 });
|
||||
resolve();
|
||||
};
|
||||
reader.onerror = () => {
|
||||
proxy?.$modal.msgError('图片插入失败');
|
||||
proxy?.$modal.closeLoading();
|
||||
const err = Object.assign(new Error('read image failed'), {
|
||||
status: 0,
|
||||
method: 'POST',
|
||||
url: options.action || ''
|
||||
});
|
||||
options.onError?.(err as any);
|
||||
reject(err);
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
// 图片失败拦截
|
||||
const handleUploadError = (err: any) => {
|
||||
proxy?.$modal.msgError('上传文件失败');
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<el-select
|
||||
v-if="userId === 1 && tenantEnabled"
|
||||
v-model="companyName"
|
||||
class="min-w-244px"
|
||||
class="min-w-244px mr-2"
|
||||
clearable
|
||||
filterable
|
||||
reserve-keyword
|
||||
@@ -34,11 +34,11 @@
|
||||
</el-tooltip>
|
||||
<!-- 消息 -->
|
||||
<el-tooltip :content="proxy.$t('navbar.message')" effect="dark" placement="bottom">
|
||||
<div>
|
||||
<div style="display:flex;align-items:center">
|
||||
<el-popover placement="bottom" trigger="click" transition="el-zoom-in-top" :width="300" :persistent="false">
|
||||
<template #reference>
|
||||
<el-badge :value="newNotice > 0 ? newNotice : ''" :max="99">
|
||||
<div class="right-menu-item hover-effect" style="display: block"><svg-icon icon-class="message" /></div>
|
||||
<div class="right-menu-item hover-effect"><svg-icon icon-class="message" /></div>
|
||||
</el-badge>
|
||||
</template>
|
||||
<template #default>
|
||||
@@ -289,9 +289,7 @@ watch(
|
||||
}
|
||||
|
||||
.right-menu {
|
||||
//float: right;
|
||||
height: 100%;
|
||||
line-height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
@@ -301,19 +299,22 @@ watch(
|
||||
}
|
||||
|
||||
.right-menu-item {
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 8px;
|
||||
height: 100%;
|
||||
height: 32px;
|
||||
font-size: 18px;
|
||||
color: var(--el-text-color-regular);
|
||||
vertical-align: text-bottom;
|
||||
border-radius: var(--app-radius-md);
|
||||
|
||||
&.hover-effect {
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
transition: background 0.2s ease, color 0.2s ease;
|
||||
|
||||
&:hover {
|
||||
background: var(--el-fill-color-lighter);
|
||||
background: var(--el-fill-color-light);
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -322,7 +323,7 @@ watch(
|
||||
margin-right: 40px;
|
||||
|
||||
.avatar-wrapper {
|
||||
margin-top: 5px;
|
||||
margin-top: 0;
|
||||
position: relative;
|
||||
|
||||
.user-avatar {
|
||||
@@ -330,7 +331,8 @@ watch(
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: var(--app-radius-md);
|
||||
margin-top: 10px;
|
||||
margin-top: 0;
|
||||
display: block;
|
||||
}
|
||||
|
||||
i {
|
||||
|
||||
@@ -75,6 +75,12 @@
|
||||
<el-switch v-model="isDark" class="drawer-switch" @change="toggleDark" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="drawer-item">
|
||||
<span>页面圆角</span>
|
||||
<span class="comp-style">
|
||||
<el-slider v-model="radiusBase" :min="0" :max="32" :step="2" style="width: 120px" @change="radiusBaseChange" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<el-divider />
|
||||
|
||||
@@ -143,6 +149,7 @@ const sideTheme = ref(settingsStore.sideTheme);
|
||||
const storeSettings = computed(() => settingsStore);
|
||||
const predefineColors = ref(['#409EFF', '#ff4500', '#ff8c00', '#ffd700', '#90ee90', '#00ced1', '#1e90ff', '#c71585']);
|
||||
const navType = ref(settingsStore.navType);
|
||||
const radiusBase = ref(settingsStore.radiusBase);
|
||||
// 是否暗黑模式
|
||||
const isDark = useDark({
|
||||
storageKey: 'useDarkKey',
|
||||
@@ -190,6 +197,16 @@ const themeChange = (val: string) => {
|
||||
settingsStore.theme = val;
|
||||
handleThemeStyle(val);
|
||||
};
|
||||
const radiusBaseChange = (val: number) => {
|
||||
settingsStore.radiusBase = val;
|
||||
const el = document.documentElement;
|
||||
el.style.setProperty('--app-radius-base', `${val}px`);
|
||||
el.style.setProperty('--app-radius-sm', `${Math.round(val * 0.6)}px`);
|
||||
el.style.setProperty('--app-radius-md', `${val}px`);
|
||||
el.style.setProperty('--app-radius-lg', `${Math.round(val * 1.4)}px`);
|
||||
el.style.setProperty('--el-border-radius-base', `${val}px`);
|
||||
el.style.setProperty('--el-border-radius-small', `${Math.round(val * 0.6)}px`);
|
||||
};
|
||||
const handleTheme = (val: string) => {
|
||||
sideTheme.value = val;
|
||||
if (isDark.value && val === SideThemeEnum.LIGHT) {
|
||||
@@ -210,6 +227,7 @@ const saveSetting = () => {
|
||||
settings.value.sideTheme = storeSettings.value.sideTheme;
|
||||
settings.value.theme = storeSettings.value.theme;
|
||||
settings.value.navType = storeSettings.value.navType;
|
||||
settings.value.radiusBase = storeSettings.value.radiusBase;
|
||||
setTimeout(() => {
|
||||
proxy?.$modal.closeLoading();
|
||||
}, 1000);
|
||||
@@ -223,6 +241,10 @@ const openSetting = () => {
|
||||
showSettings.value = true;
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
radiusBaseChange(storeSettings.value.radiusBase);
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
openSetting
|
||||
});
|
||||
|
||||
@@ -243,9 +243,9 @@ onMounted(() => {
|
||||
height: 34px;
|
||||
width: 100%;
|
||||
background-color: var(--el-bg-color);
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: var(--app-radius-md);
|
||||
box-shadow: var(--app-shadow-sm);
|
||||
border-top: none;
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
box-shadow: none;
|
||||
.tags-view-wrapper {
|
||||
.tags-view-item {
|
||||
display: inline-block;
|
||||
@@ -275,12 +275,12 @@ onMounted(() => {
|
||||
margin-right: 15px;
|
||||
}
|
||||
&.active {
|
||||
background-color: #42b983;
|
||||
background-color: var(--tags-view-active-bg);
|
||||
color: #fff;
|
||||
border-color: #42b983;
|
||||
border-color: var(--tags-view-active-border-color);
|
||||
&::before {
|
||||
content: '';
|
||||
background: #fff;
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<template>
|
||||
<div class="layout-search-dialog">
|
||||
<el-dialog v-model="state.isShowSearch" destroy-on-close :show-close="false">
|
||||
<template #footer>
|
||||
<el-dialog v-model="state.isShowSearch" destroy-on-close :show-close="false" width="600px" top="12vh">
|
||||
<div class="layout-search-dialog__content">
|
||||
<el-autocomplete
|
||||
ref="layoutMenuAutocompleteRef"
|
||||
v-model="state.menuQuery"
|
||||
class="layout-search-dialog__autocomplete"
|
||||
:fetch-suggestions="menuSearch"
|
||||
placeholder="搜索"
|
||||
:fit-input-width="true"
|
||||
@@ -20,7 +21,7 @@
|
||||
</div>
|
||||
</template>
|
||||
</el-autocomplete>
|
||||
</template>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
@@ -132,27 +133,29 @@ defineExpose({
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.layout-search-dialog {
|
||||
position: relative;
|
||||
:deep(.el-dialog) {
|
||||
padding: 0;
|
||||
border-radius: var(--app-radius-base);
|
||||
overflow: visible;
|
||||
.el-dialog__header,
|
||||
.el-dialog__body {
|
||||
.el-dialog__footer {
|
||||
display: none;
|
||||
}
|
||||
.el-dialog__footer {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
top: -53vh;
|
||||
.el-dialog__body {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
:deep(.el-autocomplete) {
|
||||
width: 560px;
|
||||
position: absolute;
|
||||
top: 150px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
|
||||
&__content {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&__autocomplete {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
:deep(.el-input__wrapper) {
|
||||
min-height: 44px;
|
||||
border-radius: var(--app-radius-md);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -127,6 +127,7 @@ const setLayout = () => {
|
||||
width: calc(100% - #{$base-sidebar-width});
|
||||
transition: width 0.28s;
|
||||
background: $fixed-header-bg;
|
||||
box-shadow: 0 2px 8px rgba(0, 21, 41, 0.10);
|
||||
}
|
||||
|
||||
.hideSidebar .fixed-header {
|
||||
|
||||
@@ -71,6 +71,11 @@ const setting: DefaultSettings = {
|
||||
/**
|
||||
* 默认布局
|
||||
*/
|
||||
layout: ''
|
||||
layout: '',
|
||||
|
||||
/**
|
||||
* 页面圆角大小
|
||||
*/
|
||||
radiusBase: 8
|
||||
};
|
||||
export default setting;
|
||||
|
||||
@@ -15,7 +15,8 @@ export const useSettingsStore = defineStore('setting', () => {
|
||||
dynamicTitle: defaultSettings.dynamicTitle,
|
||||
sideTheme: defaultSettings.sideTheme,
|
||||
theme: defaultSettings.theme,
|
||||
navType: defaultSettings.navType
|
||||
navType: defaultSettings.navType,
|
||||
radiusBase: defaultSettings.radiusBase
|
||||
});
|
||||
const title = ref<string>(defaultSettings.title);
|
||||
const theme = ref<string>(storageSetting.value.theme);
|
||||
@@ -29,6 +30,7 @@ export const useSettingsStore = defineStore('setting', () => {
|
||||
const animationEnable = ref<boolean>(defaultSettings.animationEnable);
|
||||
const dark = ref<boolean>(defaultSettings.dark);
|
||||
const navType = ref<NavTypeEnum>(storageSetting.value.navType || NavTypeEnum.LEFT);
|
||||
const radiusBase = ref<number>(storageSetting.value.radiusBase ?? defaultSettings.radiusBase);
|
||||
|
||||
const setTitle = (value: string) => {
|
||||
title.value = value;
|
||||
@@ -47,6 +49,7 @@ export const useSettingsStore = defineStore('setting', () => {
|
||||
animationEnable,
|
||||
dark,
|
||||
navType,
|
||||
radiusBase,
|
||||
setTitle
|
||||
};
|
||||
});
|
||||
|
||||
4
src/types/global.d.ts
vendored
4
src/types/global.d.ts
vendored
@@ -123,6 +123,10 @@ declare global {
|
||||
* 主题模式
|
||||
*/
|
||||
theme: string;
|
||||
/**
|
||||
* 页面圆角大小
|
||||
*/
|
||||
radiusBase: number;
|
||||
}
|
||||
|
||||
declare interface DefaultSettings extends LayoutSetting {
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
* 部署方式 Docker 容器编排 一键部署业务集群<br />
|
||||
* 国际化 SpringMessage Spring标准国际化方案<br />
|
||||
</p>
|
||||
<p><b>当前版本:</b> <span>v5.5.3</span></p>
|
||||
<p><b>当前版本:</b> <span>v5.6.1</span></p>
|
||||
<p>
|
||||
<el-tag type="danger">¥免费开源</el-tag>
|
||||
</p>
|
||||
@@ -77,7 +77,7 @@
|
||||
* 分布式监控 Prometheus、Grafana 全方位性能监控<br />
|
||||
* 其余与 Vue 版本一致<br />
|
||||
</p>
|
||||
<p><b>当前版本:</b> <span>v2.5.3</span></p>
|
||||
<p><b>当前版本:</b> <span>v2.6.1</span></p>
|
||||
<p>
|
||||
<el-tag type="danger">¥免费开源</el-tag>
|
||||
</p>
|
||||
|
||||
@@ -42,19 +42,19 @@
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" icon="Plus" @click="handleAdd()">添加</el-button>
|
||||
<el-button v-hasPermi="['workflow:definition:add']" type="primary" icon="Plus" @click="handleAdd()">添加</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="success" icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
|
||||
<el-button v-hasPermi="['workflow:definition:edit']" type="success" icon="Edit" :disabled="single" @click="handleUpdate()">修改</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" icon="Delete" :disabled="multiple" @click="handleDelete()">删除</el-button>
|
||||
<el-button v-hasPermi="['workflow:definition:remove']" type="danger" icon="Delete" :disabled="multiple" @click="handleDelete()">删除</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" icon="UploadFilled" @click="uploadDialog.visible = true">部署流程文件</el-button>
|
||||
<el-button v-hasPermi="['workflow:definition:import']" type="primary" icon="UploadFilled" @click="uploadDialog.visible = true">部署流程文件</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="warning" icon="Download" :disabled="single" @click="handleExportDef">导出</el-button>
|
||||
<el-button v-hasPermi="['workflow:definition:export']" type="warning" icon="Download" :disabled="single" @click="handleExportDef">导出</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
|
||||
</el-row>
|
||||
@@ -74,6 +74,7 @@
|
||||
<el-table-column align="center" prop="activityStatus" label="激活状态" width="130">
|
||||
<template #default="scope">
|
||||
<el-switch
|
||||
v-hasPermi="['workflow:definition:active']"
|
||||
v-model="scope.row.activityStatus"
|
||||
:active-value="1"
|
||||
:inactive-value="0"
|
||||
@@ -92,21 +93,21 @@
|
||||
<template #default="scope">
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button link type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">删除流程</el-button>
|
||||
<el-button v-hasPermi="['workflow:definition:remove']" link type="primary" size="small" icon="Delete" @click="handleDelete(scope.row)">删除流程</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button link type="primary" size="small" icon="CopyDocument" @click="handleCopyDef(scope.row)">复制流程</el-button>
|
||||
<el-button v-hasPermi="['workflow:definition:copy']" link type="primary" size="small" icon="CopyDocument" @click="handleCopyDef(scope.row)">复制流程</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button link type="primary" v-if="scope.row.isPublish === 0" icon="Pointer" size="small" @click="design(scope.row)"
|
||||
<el-button v-hasPermi="['workflow:definition:query']" link type="primary" v-if="scope.row.isPublish === 0" icon="Pointer" size="small" @click="design(scope.row)"
|
||||
>流程设计</el-button
|
||||
>
|
||||
<el-button link type="primary" v-else icon="View" size="small" @click="designView(scope.row)">查看流程</el-button>
|
||||
<el-button v-hasPermi="['workflow:definition:query']" link type="primary" v-else icon="View" size="small" @click="designView(scope.row)">查看流程</el-button>
|
||||
</el-col>
|
||||
<el-col v-if="scope.row.isPublish !== 1" :span="1.5">
|
||||
<el-button link type="primary" size="small" icon="CircleCheck" @click="handlePublish(scope.row)">发布流程</el-button>
|
||||
<el-button v-hasPermi="['workflow:definition:publish']" link type="primary" size="small" icon="CircleCheck" @click="handlePublish(scope.row)">发布流程</el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
<template #header>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete">删除</el-button>
|
||||
<el-button v-hasPermi="['workflow:instance:remove']" type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete">删除</el-button>
|
||||
</el-col>
|
||||
<right-toolbar v-model:show-search="showSearch" @query-table="handleQuery"></right-toolbar>
|
||||
</el-row>
|
||||
@@ -75,7 +75,7 @@
|
||||
<el-table-column align="center" prop="version" label="版本号" width="90">
|
||||
<template #default="scope"> v{{ scope.row.version }}.0</template>
|
||||
</el-table-column>
|
||||
<el-table-column v-if="tab === 'running'" align="center" prop="isSuspended" label="状态" min-width="70">
|
||||
<el-table-column v-if="tab === 'running'" v-hasPermi="['workflow:instance:active']" align="center" prop="isSuspended" label="状态" min-width="70">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="!scope.row.isSuspended" type="success">激活</el-tag>
|
||||
<el-tag v-else type="danger">挂起</el-tag>
|
||||
@@ -104,15 +104,15 @@
|
||||
</el-popover>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="danger" size="small" icon="Delete" @click="handleDelete(scope.row)">删除 </el-button>
|
||||
<el-button v-hasPermi="['workflow:instance:remove']" type="danger" size="small" icon="Delete" @click="handleDelete(scope.row)">删除 </el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="10" class="mb8">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" size="small" icon="View" @click="handleView(scope.row)">查看</el-button>
|
||||
<el-button v-hasPermi="['workflow:instance:query']" type="primary" size="small" icon="View" @click="handleView(scope.row)">查看</el-button>
|
||||
</el-col>
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" size="small" icon="Document" @click="handleInstanceVariable(scope.row)"> 变量 </el-button>
|
||||
<el-button v-hasPermi="['workflow:instance:variableQuery']" type="primary" size="small" icon="Document" @click="handleInstanceVariable(scope.row)"> 变量 </el-button>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
@@ -138,7 +138,7 @@
|
||||
<el-table-column align="center" prop="version" label="版本号" width="90">
|
||||
<template #default="scope"> v{{ scope.row.version }}.0</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" prop="suspensionState" label="状态" min-width="70">
|
||||
<el-table-column v-hasPermi="['workflow:instance:active']" align="center" prop="suspensionState" label="状态" min-width="70">
|
||||
<template #default="scope">
|
||||
<el-tag v-if="scope.row.suspensionState == 1" type="success">激活</el-tag>
|
||||
<el-tag v-else type="danger">挂起</el-tag>
|
||||
@@ -170,7 +170,7 @@
|
||||
<el-input v-model="form.value" placeholder="请输入变量值" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleVariable(ruleFormRef)">确认</el-button>
|
||||
<el-button v-hasPermi="['workflow:instance:variable']" type="primary" @click="handleVariable(ruleFormRef)">确认</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
@@ -12,7 +12,9 @@ export default (path: any) => {
|
||||
},
|
||||
resolvers: [
|
||||
// 自动导入 Element Plus 相关函数ElMessage, ElMessageBox... (带样式)
|
||||
ElementPlusResolver()
|
||||
ElementPlusResolver({
|
||||
importStyle: false
|
||||
})
|
||||
],
|
||||
vueTemplate: true, // 是否在 vue 模板中自动导入
|
||||
dts: path.resolve(path.resolve(__dirname, '../../src'), 'types', 'auto-imports.d.ts')
|
||||
|
||||
@@ -6,7 +6,9 @@ export default (path: any) => {
|
||||
return Components({
|
||||
resolvers: [
|
||||
// 自动导入 Element Plus 组件
|
||||
ElementPlusResolver(),
|
||||
ElementPlusResolver({
|
||||
importStyle: false
|
||||
}),
|
||||
// 自动注册图标组件
|
||||
IconsResolver({
|
||||
enabledCollections: ['ep']
|
||||
|
||||
@@ -1,28 +1,110 @@
|
||||
import compression from 'vite-plugin-compression';
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
import zlib from 'zlib';
|
||||
import { promisify } from 'util';
|
||||
import type { Plugin, ResolvedConfig } from 'vite';
|
||||
|
||||
export default (env: any) => {
|
||||
const { VITE_BUILD_COMPRESS } = env;
|
||||
const plugin: any[] = [];
|
||||
if (VITE_BUILD_COMPRESS) {
|
||||
const compressList = VITE_BUILD_COMPRESS.split(',');
|
||||
if (compressList.includes('gzip')) {
|
||||
// http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件
|
||||
plugin.push(
|
||||
compression({
|
||||
ext: '.gz',
|
||||
deleteOriginFile: false
|
||||
})
|
||||
);
|
||||
}
|
||||
if (compressList.includes('brotli')) {
|
||||
plugin.push(
|
||||
compression({
|
||||
ext: '.br',
|
||||
algorithm: 'brotliCompress',
|
||||
deleteOriginFile: false
|
||||
})
|
||||
);
|
||||
}
|
||||
const gzip = promisify(zlib.gzip);
|
||||
const brotliCompress = promisify(zlib.brotliCompress);
|
||||
const compressibleFileRE = /\.(js|mjs|json|css|html)$/i;
|
||||
const defaultThreshold = 1025;
|
||||
|
||||
type CompressionKind = 'gzip' | 'brotli';
|
||||
|
||||
const compressionHandlers: Record<CompressionKind, { ext: string; compress: (content: Buffer) => Promise<Buffer> }> = {
|
||||
gzip: {
|
||||
ext: '.gz',
|
||||
compress: (content) => gzip(content, { level: zlib.constants.Z_BEST_COMPRESSION })
|
||||
},
|
||||
brotli: {
|
||||
ext: '.br',
|
||||
compress: (content) =>
|
||||
brotliCompress(content, {
|
||||
params: {
|
||||
[zlib.constants.BROTLI_PARAM_QUALITY]: zlib.constants.BROTLI_MAX_QUALITY,
|
||||
[zlib.constants.BROTLI_PARAM_MODE]: zlib.constants.BROTLI_MODE_TEXT
|
||||
}
|
||||
})
|
||||
}
|
||||
return plugin;
|
||||
};
|
||||
|
||||
async function collectFiles(rootDir: string): Promise<string[]> {
|
||||
const entries = await fs.readdir(rootDir, { withFileTypes: true });
|
||||
const files = await Promise.all(
|
||||
entries.map(async (entry) => {
|
||||
const fullPath = path.join(rootDir, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
return collectFiles(fullPath);
|
||||
}
|
||||
return compressibleFileRE.test(entry.name) ? [fullPath] : [];
|
||||
})
|
||||
);
|
||||
return files.flat();
|
||||
}
|
||||
|
||||
function createCompressionPlugin(kind: CompressionKind): Plugin {
|
||||
const handler = compressionHandlers[kind];
|
||||
let config: ResolvedConfig | undefined;
|
||||
|
||||
return {
|
||||
name: `local:compression:${kind}`,
|
||||
apply: 'build',
|
||||
enforce: 'post',
|
||||
configResolved(resolvedConfig) {
|
||||
config = resolvedConfig;
|
||||
},
|
||||
async closeBundle() {
|
||||
const outputDir = path.resolve(process.cwd(), config?.build.outDir ?? 'dist');
|
||||
const files = await collectFiles(outputDir);
|
||||
const compressedEntries: Array<{ file: string; originalKb: string; compressedKb: string }> = [];
|
||||
|
||||
await Promise.all(
|
||||
files.map(async (filePath) => {
|
||||
const stat = await fs.stat(filePath);
|
||||
if (stat.size < defaultThreshold) {
|
||||
return;
|
||||
}
|
||||
|
||||
const content = await fs.readFile(filePath);
|
||||
const compressed = await handler.compress(content);
|
||||
const outputFile = `${filePath}${handler.ext}`;
|
||||
|
||||
await fs.writeFile(outputFile, compressed);
|
||||
compressedEntries.push({
|
||||
file: path.relative(outputDir, outputFile).replaceAll('\\', '/'),
|
||||
originalKb: (stat.size / 1024).toFixed(2),
|
||||
compressedKb: (compressed.byteLength / 1024).toFixed(2)
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
if (!compressedEntries.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
compressedEntries.sort((a, b) => a.file.localeCompare(b.file));
|
||||
config?.logger.info(`\n[compression:${kind}] generated ${compressedEntries.length} files`);
|
||||
for (const entry of compressedEntries) {
|
||||
config?.logger.info(`${path.basename(outputDir)}/${entry.file} ${entry.originalKb}kb -> ${entry.compressedKb}kb`);
|
||||
}
|
||||
config?.logger.info('');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default (env: Record<string, string>) => {
|
||||
const { VITE_BUILD_COMPRESS } = env;
|
||||
const plugins: Plugin[] = [];
|
||||
if (!VITE_BUILD_COMPRESS) {
|
||||
return plugins;
|
||||
}
|
||||
|
||||
const compressionList = VITE_BUILD_COMPRESS.split(',').map((item) => item.trim()) as CompressionKind[];
|
||||
if (compressionList.includes('gzip')) {
|
||||
plugins.push(createCompressionPlugin('gzip'));
|
||||
}
|
||||
if (compressionList.includes('brotli')) {
|
||||
plugins.push(createCompressionPlugin('brotli'));
|
||||
}
|
||||
return plugins;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user