diff --git a/LICENSE b/LICENSE
index 9825cba0..1ffeefc1 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,5 +1,6 @@
MIT License
+Copyright (c) 2017-present PanJiaChen
Copyright (c) 2021-present 有来开源组织
Permission is hereby granted, free of charge, to any person obtaining a copy
diff --git a/README.en-US.md b/README.en-US.md
index a29b07b4..e83b7f26 100644
--- a/README.en-US.md
+++ b/README.en-US.md
@@ -226,17 +226,17 @@ Thanks to all the contributors!
- 
+ 
WeChat Official Account
|
|
- 
+ 
Mini Program
|
|
- 
+ 
Add Author WeChat
|
diff --git a/README.md b/README.md
index 94cf9cb4..1b7ebb8d 100644
--- a/README.md
+++ b/README.md
@@ -223,17 +223,17 @@ Thanks to all the contributors!
- 
+ 
公众号「有来技术」
|
|
- 
+ 
小程序「有来技术」
|
|
- 
+ 
添加作者微信
|
diff --git a/deploy.mjs b/deploy.mjs
new file mode 100644
index 00000000..977f29b0
--- /dev/null
+++ b/deploy.mjs
@@ -0,0 +1,33 @@
+/**
+ * 部署管理后台到服务器 | 用法:pnpm run deploy
+ *
+ * 前置:SSH 免密登录(只需一次)
+ * 1. 如果 %USERPROFILE%\.ssh\id_rsa 已存在跳过,否则 ssh-keygen -t rsa 回车到底
+ *
+ * 2. 拷贝公钥到服务器(会提示输一次服务器密码):
+ * Windows: type %USERPROFILE%\.ssh\id_rsa.pub | ssh root@server "cat >> ~/.ssh/authorized_keys"
+ * Mac: ssh-copy-id root@server
+ *
+ * 3. ssh root@server 输完不弹密码直接连上即成功
+ */
+import { execSync } from "node:child_process";
+
+
+
+// ==================== 改为你的服务器信息 ====================
+const HOST = "127.0.0.1"; // 服务器 IP,改为你的
+const USER = "root"; // SSH 用户名,改为你的
+const PORT = 22; // SSH 端口,改为你的
+const TARGET = "/data/www/vue3-element-admin"; // 部署目录,改为你的
+// =========================================================
+
+console.log("构建中...");
+execSync("pnpm build-only", { stdio: "inherit" });
+
+console.log(`部署 → ${USER}@${HOST}:${TARGET}`);
+execSync(
+ `scp -o StrictHostKeyChecking=no -P ${PORT} -r "dist/." "${USER}@${HOST}:${TARGET}/"`,
+ { stdio: "inherit", shell: true }
+);
+
+console.log("部署完成");
diff --git a/licenses/vue-element-admin/LICENSE b/licenses/vue-element-admin/LICENSE
deleted file mode 100644
index 3fce3848..00000000
--- a/licenses/vue-element-admin/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2017-present PanJiaChen
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
\ No newline at end of file
diff --git a/licenses/vue3-element-admin/LICENSE b/licenses/vue3-element-admin/LICENSE
deleted file mode 100644
index 9825cba0..00000000
--- a/licenses/vue3-element-admin/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2021-present 有来开源组织
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/package.json b/package.json
index ae322c28..684efb1f 100644
--- a/package.json
+++ b/package.json
@@ -17,7 +17,8 @@
"lint": "npm run lint:eslint && npm run lint:prettier && npm run lint:stylelint",
"preinstall": "npx only-allow pnpm",
"prepare": "husky",
- "commit": "git-cz"
+ "commit": "git-cz",
+ "deploy": "node deploy.mjs"
},
"config": {
"commitizen": {
diff --git a/src/assets/images/preview/app-01.png b/public/images/preview/app-01.png
similarity index 100%
rename from src/assets/images/preview/app-01.png
rename to public/images/preview/app-01.png
diff --git a/src/assets/images/preview/app-02.png b/public/images/preview/app-02.png
similarity index 100%
rename from src/assets/images/preview/app-02.png
rename to public/images/preview/app-02.png
diff --git a/src/assets/images/preview/app-03.png b/public/images/preview/app-03.png
similarity index 100%
rename from src/assets/images/preview/app-03.png
rename to public/images/preview/app-03.png
diff --git a/src/assets/images/preview/app-04.png b/public/images/preview/app-04.png
similarity index 100%
rename from src/assets/images/preview/app-04.png
rename to public/images/preview/app-04.png
diff --git a/src/assets/images/preview/pc-01.png b/public/images/preview/pc-01.png
similarity index 100%
rename from src/assets/images/preview/pc-01.png
rename to public/images/preview/pc-01.png
diff --git a/src/assets/images/preview/pc-02.png b/public/images/preview/pc-02.png
similarity index 100%
rename from src/assets/images/preview/pc-02.png
rename to public/images/preview/pc-02.png
diff --git a/src/assets/images/preview/pc-03.png b/public/images/preview/pc-03.png
similarity index 100%
rename from src/assets/images/preview/pc-03.png
rename to public/images/preview/pc-03.png
diff --git a/src/assets/images/preview/pc-04.png b/public/images/preview/pc-04.png
similarity index 100%
rename from src/assets/images/preview/pc-04.png
rename to public/images/preview/pc-04.png
diff --git a/src/assets/images/preview/pc-05.png b/public/images/preview/pc-05.png
similarity index 100%
rename from src/assets/images/preview/pc-05.png
rename to public/images/preview/pc-05.png
diff --git a/src/assets/images/preview/pc-06.png b/public/images/preview/pc-06.png
similarity index 100%
rename from src/assets/images/preview/pc-06.png
rename to public/images/preview/pc-06.png
diff --git a/src/assets/images/qr/wechat-mp.jpg b/public/images/qr/wechat-mp.jpg
similarity index 100%
rename from src/assets/images/qr/wechat-mp.jpg
rename to public/images/qr/wechat-mp.jpg
diff --git a/src/assets/images/qr/wechat-official.png b/public/images/qr/wechat-official.png
similarity index 100%
rename from src/assets/images/qr/wechat-official.png
rename to public/images/qr/wechat-official.png
diff --git a/src/assets/images/qr/wechat-personal.png b/public/images/qr/wechat-personal.png
similarity index 100%
rename from src/assets/images/qr/wechat-personal.png
rename to public/images/qr/wechat-personal.png
diff --git a/src/layouts/components/LayoutTagsView.vue b/src/layouts/components/LayoutTagsView.vue
index 2eac8eb6..20270657 100644
--- a/src/layouts/components/LayoutTagsView.vue
+++ b/src/layouts/components/LayoutTagsView.vue
@@ -49,10 +49,8 @@
-
-
-
-
+
+
@@ -62,39 +60,27 @@
-
-
-
+
刷新当前
-
-
-
+
关闭当前
-
-
-
-
+
+
关闭其它
-
-
-
-
+
+
关闭左侧
-
-
-
-
+
+
关闭右侧
-
-
-
-
+
+
关闭所有
@@ -114,25 +100,25 @@
class="contextmenu__danger"
@click="closeSelectedTag(selectedTag)"
>
-
+
关闭
-
+
关闭其它
-
+
关闭左侧
-
+
关闭右侧
-
+
关闭所有
@@ -212,16 +198,16 @@ const handleActionCommand = (command: string) => {
case "closeCurrent":
closeSelectedTag(currentTag.value);
break;
- case "closeOthers":
+ case "closeOtherTags":
closeOtherTagsForActive();
break;
- case "closeLeft":
+ case "closeLeftTags":
closeLeftTagsForActive();
break;
- case "closeRight":
+ case "closeRightTags":
closeRightTagsForActive();
break;
- case "closeAll":
+ case "closeAllTags":
closeAllTags(currentTag.value);
break;
}
@@ -379,9 +365,7 @@ const closeSelectedTag = (tag: TagView | null) => {
});
};
-/* ------------------------------------------------------------------ */
-/* 批量关闭标签(左/右/其它方向) */
-/* ------------------------------------------------------------------ */
+// 批量关闭标签(左/右/其它方向)
/**
* 按方向批量关闭标签的通用执行器。
@@ -407,7 +391,9 @@ function closeDirectional(
});
}
-/** 关闭当前激活标签之外的其它标签 */
+/**
+ * 关闭当前激活标签之外的其它标签。
+ */
const closeOtherTagsForActive = () => {
if (!currentTag.value) return;
tagsViewStore.delOtherViews(currentTag.value).then(() => {
@@ -646,19 +632,33 @@ useContextMenuManager();
background-color: var(--el-fill-color-light);
}
- :deep(.el-icon) {
+ :deep(.el-icon),
+ :deep(div) {
opacity: 0.45;
}
- &:hover :deep(.el-icon) {
+ &:hover :deep(.el-icon),
+ &:hover :deep(div) {
opacity: 0.7;
}
}
+}
- &__dropdown-icon {
- flex-shrink: 0;
- margin-right: 6px;
- opacity: 0.55;
+.icon-16 {
+ width: 16px;
+ height: 16px;
+}
+
+.icon-14 {
+ flex-shrink: 0;
+ width: 14px;
+ height: 14px;
+ margin-right: 6px;
+ opacity: 0.55;
+
+ &.el-icon {
+ width: 14px;
+ height: 14px;
}
}
@@ -697,6 +697,8 @@ useContextMenuManager();
&__icon {
flex-shrink: 0;
+ width: 16px;
+ height: 16px;
opacity: 0.55;
}
diff --git a/src/layouts/composables/useMixMenu.ts b/src/layouts/composables/useMixMenu.ts
index 1e4bcf12..061254bd 100644
--- a/src/layouts/composables/useMixMenu.ts
+++ b/src/layouts/composables/useMixMenu.ts
@@ -1,13 +1,5 @@
/**
* 混合布局菜单逻辑
- *
- * 从 MixLayout.vue 提取,负责:
- * - 顶部菜单项扁平化(单子菜单向上合并 title/icon)
- * - 侧边菜单路径解析与激活判定
- * - 菜单切换导航(自动跳转到第一个可访问子菜单)
- * - 路由变化时同步顶部菜单激活态与侧边菜单数据
- *
- * 模板只绑定返回值,无需关心内部实现。
*/
import type { LocationQueryRaw, RouteRecordRaw } from "vue-router";
import { useLayout } from "../useLayout";
@@ -15,10 +7,6 @@ import { useAppStore, usePermissionStore, useSettingsStore } from "@/stores";
import { isExternal } from "@/utils/index";
import { ElIcon } from "element-plus";
-/* ------------------------------------------------------------------ */
-/* MenuIcon — 菜单图标渲染组件(Element Plus / SVG 双模式) */
-/* ------------------------------------------------------------------ */
-
const MenuIcon = defineComponent({
props: { icon: String },
setup(props) {
@@ -39,9 +27,6 @@ const MenuIcon = defineComponent({
},
});
-/* ------------------------------------------------------------------ */
-/* useMixMenu */
-/* ------------------------------------------------------------------ */
export function useMixMenu() {
const route = useRoute();
@@ -53,7 +38,6 @@ export function useMixMenu() {
const { activeTopMenuPath, sideMenuRoutes } = useLayout();
- /* ------ 顶部菜单 ------------------------------------------------- */
/**
* 顶部菜单项。
@@ -84,9 +68,12 @@ export function useMixMenu() {
});
});
- /* ------ 侧边菜单 ------------------------------------------------- */
- /** 当前路由对应的侧边菜单激活路径(优先取 meta.activeMenu) */
+ /**
+ * 当前路由对应的侧边菜单激活路径。
+ *
+ * 优先取 meta.activeMenu,否则使用当前路由路径。
+ */
const activeSideMenuPath = computed(() => {
const { meta, path } = route;
return typeof meta?.activeMenu === "string" ? meta.activeMenu : path;
@@ -105,8 +92,6 @@ export function useMixMenu() {
return `${activeTopMenuPath.value}/${routePath}`;
}
- /* ------ 路径工具 ------------------------------------------------- */
-
/**
* 从完整路径中提取第一段作为顶级菜单路径。
*
@@ -114,14 +99,15 @@ export function useMixMenu() {
* "/dashboard" → "/"
*/
function extractTopMenuPath(path: string): string {
- return path.split("/").filter(Boolean).length > 1
- ? path.match(/^\/[^/]+/)?.[0] || "/"
- : "/";
+ return path.split("/").filter(Boolean).length > 1 ? path.match(/^\/[^/]+/)?.[0] || "/" : "/";
}
- /* ------ 菜单切换 ------------------------------------------------- */
- /** 顶部菜单点击:切换侧边菜单数据,并导航到第一个可访问叶子菜单 */
+ /**
+ * 顶部菜单点击。
+ *
+ * 切换侧边菜单数据,并导航到第一个可访问叶子菜单。
+ */
function handleTopMenuSelect(menuPath: string) {
if (menuPath === activeTopMenuPath.value) return;
@@ -153,7 +139,7 @@ export function useMixMenu() {
}
}
- /* ------ 路由同步 ------------------------------------------------- */
+ // 路由同步
/**
* 监听路由变化,保持顶部菜单激活态和侧边菜单数据同步。
diff --git a/src/layouts/useLayout.ts b/src/layouts/useLayout.ts
index fab4c286..20855c79 100644
--- a/src/layouts/useLayout.ts
+++ b/src/layouts/useLayout.ts
@@ -58,16 +58,30 @@ export function useLayout() {
// 菜单数据
// ============================================
- /** 路由列表(左侧/顶部菜单) */
+ /**
+ * 路由列表。
+ *
+ * 左侧/顶部菜单共用,由权限 store 生成。
+ */
const routes = computed(() => permissionStore.routes);
- /** 混合布局侧边菜单 */
+ /**
+ * 混合布局侧边菜单。
+ *
+ * 根据当前激活的顶部菜单路径动态计算。
+ */
const sideMenuRoutes = computed(() => permissionStore.mixLayoutSideMenus);
- /** 顶部菜单激活路径 */
+ /**
+ * 顶部菜单激活路径。
+ */
const activeTopMenuPath = computed(() => appStore.activeTopMenuPath);
- /** 当前激活菜单 */
+ /**
+ * 当前激活菜单。
+ *
+ * 优先取路由 meta.activeMenu,否则取当前路径。
+ */
const activeMenu = computed(() => {
const { meta, path } = route;
return meta?.activeMenu || path;
diff --git a/src/types/api/index.ts b/src/types/api/index.ts
deleted file mode 100644
index 139e9247..00000000
--- a/src/types/api/index.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-export interface PageQueryParams {
- pageNum: number;
- pageSize: number;
- [key: string]: any;
-}
-
-export interface OptionItem {
- label: string;
- value: string | number;
- meta?: any;
- children?: OptionItem[];
-}
-
-export interface ExcelResult {
- /** 状态码 */
- code: number;
- /** 无效数据条数 */
- invalidCount: number;
- /** 有效数据条数 */
- validCount: number;
- /** 错误信息 */
- messageList: Array;
-}
-
-export interface PageResult {
- total: number;
- list: T[];
- params: PageQueryParams;
-}
-
-export interface ApiResponse {
- code: number | string;
- msg: string;
- data: T;
-}
-
-export interface BaseEntity {
- id: number;
- createTime: string;
- updateTime: string;
-}
diff --git a/src/types/global.d.ts b/src/types/global.d.ts
index cfe67152..86453887 100644
--- a/src/types/global.d.ts
+++ b/src/types/global.d.ts
@@ -4,11 +4,6 @@
* @deprecated 请使用 @/types 下的具名导出
*/
declare global {
- type ApiResponse = import("@/types/api").ApiResponse;
- type BaseQueryParams = import("@/types/api").BaseQueryParams;
- type PageResult = import("@/types/api").PageResult;
- type OptionItem = import("@/types/api").OptionItem;
- type ExcelResult = import("@/types/api").ExcelResult;
type TagView = import("@/types/ui").TagView;
type AppSettings = import("@/types/ui").AppSettings;
}
diff --git a/src/utils/download.ts b/src/utils/download.ts
index f1f6ac6a..35d99073 100644
--- a/src/utils/download.ts
+++ b/src/utils/download.ts
@@ -44,7 +44,7 @@ function extractFileName(contentDisposition: string): string {
* downloadFile(response, "用户列表.xlsx");
* ```
*/
-export function downloadFile(response: any, customFileName?: string): void {
+export function downloadFile(response: { data: any; headers: Record }, customFileName?: string): void {
try {
const fileData = response.data;
const contentDisposition = response.headers["content-disposition"];
diff --git a/src/views/dashboard/index.vue b/src/views/dashboard/index.vue
index 6f0a4ad8..740259b5 100644
--- a/src/views/dashboard/index.vue
+++ b/src/views/dashboard/index.vue
@@ -579,11 +579,8 @@ $radius: 10px;
&__end {
display: flex;
- gap: 16px;
+ gap: 20px;
align-items: center;
- padding: 10px 14px;
- background: var(--el-color-primary-light-9);
- border-radius: 8px;
}
}
diff --git a/src/views/system/config/index.vue b/src/views/system/config/index.vue
index e945660c..c1f210a5 100644
--- a/src/views/system/config/index.vue
+++ b/src/views/system/config/index.vue
@@ -31,7 +31,7 @@
diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue
index 6915b65f..c7ae09f0 100644
--- a/src/views/system/role/index.vue
+++ b/src/views/system/role/index.vue
@@ -328,17 +328,10 @@ function handleQuery(): void {
}
/**
- * 重置查询条件
- */
-function resetQuery(): void {
- queryFormRef.value?.resetFields();
-}
-
-/**
- * 重置查询条件并重新查询
+ * 重置搜索条件并重新查询。
*/
function handleResetQuery(): void {
- resetQuery();
+ queryFormRef.value?.resetFields();
handleQuery();
}
diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue
index aa49ce66..b3f010f0 100644
--- a/src/views/system/user/index.vue
+++ b/src/views/system/user/index.vue
@@ -78,7 +78,7 @@
导入
-
+
导出
@@ -347,19 +347,12 @@ function handleQuery(): void {
}
/**
- * 重置查询条件
+ * 重置搜索条件并重新查询。
*/
-function resetQuery(): void {
+function handleResetQuery(): void {
queryFormRef.value?.resetFields();
tableData.params.deptId = undefined;
tableData.params.createTime = undefined;
-}
-
-/**
- * 重置查询条件并重新查询
- */
-function handleResetQuery(): void {
- resetQuery();
handleQuery();
}
@@ -509,10 +502,7 @@ function handleDelete(id?: string): void {
);
}
-/**
- * 导出用户列表
- */
-async function exportUsers(): Promise {
+async function handleExport(): Promise {
const response = await UserAPI.export(tableData.params);
downloadFile(response);
ElMessage.success("导出成功");