-
+
+
+
diff --git a/src/layouts/modules/theme-drawer/modules/page-fun.vue b/src/layouts/modules/theme-drawer/modules/page-fun.vue
index ef48adc0..aa0aadab 100644
--- a/src/layouts/modules/theme-drawer/modules/page-fun.vue
+++ b/src/layouts/modules/theme-drawer/modules/page-fun.vue
@@ -130,6 +130,9 @@ const isWrapperScrollMode = computed(() => themeStore.layout.scrollMode === 'wra
+
+
+
diff --git a/src/layouts/modules/theme-drawer/modules/table-props.vue b/src/layouts/modules/theme-drawer/modules/table-props.vue
new file mode 100644
index 00000000..ac84e5ce
--- /dev/null
+++ b/src/layouts/modules/theme-drawer/modules/table-props.vue
@@ -0,0 +1,44 @@
+
+
+
+ {{ $t('theme.tablePropsTitle') }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/locales/langs/en-us.ts b/src/locales/langs/en-us.ts
index acf2d6a4..92be6f67 100644
--- a/src/locales/langs/en-us.ts
+++ b/src/locales/langs/en-us.ts
@@ -135,6 +135,9 @@ const local: App.I18n.Schema = {
},
multilingual: {
visible: 'Display multilingual button'
+ },
+ globalSearch: {
+ visible: 'Display GlobalSearch button'
}
},
tab: {
@@ -165,6 +168,20 @@ const local: App.I18n.Schema = {
visible: 'Watermark Full Screen Visible',
text: 'Watermark Text'
},
+ tablePropsTitle: 'Table Props',
+ table: {
+ size: {
+ title: 'Table Size',
+ small: 'Small',
+ medium: 'Medium',
+ large: 'Large'
+ },
+ bordered: 'Bordered',
+ bottomBordered: 'Bottom Bordered',
+ singleColumn: 'Single Column',
+ singleLine: 'Single Line',
+ striped: 'Striped'
+ },
themeDrawerTitle: 'Theme Configuration',
pageFunTitle: 'Page Function',
resetCacheStrategy: {
@@ -222,6 +239,13 @@ const local: App.I18n.Schema = {
'workflow_process-instance': 'Process Instance',
workflow_leave: 'Leave Apply'
},
+ dict: {
+ sys_user_sex: {
+ male: 'Male',
+ female: 'Female',
+ unknown: 'Unknown'
+ }
+ },
page: {
login: {
common: {
@@ -579,6 +603,7 @@ const local: App.I18n.Schema = {
buttonPermissionList: 'Button Permission List',
emptyMenu: 'Empty Menu',
menuDetail: 'Menu Detail',
+ cascadeDeleteContent: 'Cascade delete menu will delete the selected menu and all its sub-menus, are you sure?',
iconifyTip: 'iconify address:`https://icones.js.org`',
isFrameTip: 'If you choose External Link, the routing address needs to start with `http(s)://`',
isCacheTip:
@@ -603,6 +628,10 @@ const local: App.I18n.Schema = {
required: 'Please select Menu Icon',
invalid: 'Menu Icon cannot be empty'
},
+ menuIds: {
+ required: 'Please select Menu',
+ invalid: 'Menu cannot be empty'
+ },
menuName: {
required: 'Please enter Menu Name',
invalid: 'Menu Name cannot be empty'
@@ -660,7 +689,8 @@ const local: App.I18n.Schema = {
button: 'Button',
addMenu: 'Add Menu',
addChildMenu: 'Add Child Menu',
- editMenu: 'Edit Menu'
+ editMenu: 'Edit Menu',
+ cascadeDelete: 'Cascade Delete Menu'
},
notice: {
title: 'Notice List',
diff --git a/src/locales/langs/zh-cn.ts b/src/locales/langs/zh-cn.ts
index 6faa420d..64a840d9 100644
--- a/src/locales/langs/zh-cn.ts
+++ b/src/locales/langs/zh-cn.ts
@@ -135,6 +135,9 @@ const local: App.I18n.Schema = {
},
multilingual: {
visible: '显示多语言按钮'
+ },
+ globalSearch: {
+ visible: '显示全局搜索按钮'
}
},
tab: {
@@ -165,6 +168,20 @@ const local: App.I18n.Schema = {
visible: '显示全屏水印',
text: '水印文本'
},
+ tablePropsTitle: '表格配置',
+ table: {
+ size: {
+ title: '表格大小',
+ small: '小',
+ medium: '中',
+ large: '大'
+ },
+ bordered: '边框',
+ bottomBordered: '底部边框',
+ singleColumn: '设定行的分割线',
+ singleLine: '设定列的分割线',
+ striped: '斑马线条纹'
+ },
themeDrawerTitle: '主题配置',
pageFunTitle: '页面功能',
resetCacheStrategy: {
@@ -222,6 +239,13 @@ const local: App.I18n.Schema = {
'workflow_process-instance': '流程实例',
workflow_leave: '请假申请'
},
+ dict: {
+ sys_user_sex: {
+ male: '男',
+ female: '女',
+ unknown: '未知'
+ }
+ },
page: {
login: {
common: {
@@ -579,6 +603,7 @@ const local: App.I18n.Schema = {
buttonPermissionList: '按钮权限列表',
emptyMenu: '暂无菜单',
menuDetail: '菜单详情',
+ cascadeDeleteContent: '级联删除菜单将删除所选中的菜单,是否继续?',
iconifyTip: 'iconify 地址:https://icones.js.org',
isFrameTip: '选择是外链则路由地址需要以`http(s)://`开头',
isCacheTip: '选择是则会被`keep-alive`缓存,需要匹配组件的`name`和地址保持一致',
@@ -597,6 +622,10 @@ const local: App.I18n.Schema = {
required: '请选择菜单类型',
invalid: '菜单类型不能为空'
},
+ menuIds: {
+ required: '请选择菜单',
+ invalid: '菜单不能为空'
+ },
icon: {
required: '请选择菜单图标',
invalid: '菜单图标不能为空'
@@ -658,7 +687,8 @@ const local: App.I18n.Schema = {
button: '按钮',
addMenu: '新增菜单',
addChildMenu: '新增子菜单',
- editMenu: '编辑菜单'
+ editMenu: '编辑菜单',
+ cascadeDelete: '级联删除菜单'
},
notice: {
title: '通知公告列表',
diff --git a/src/plugins/app.ts b/src/plugins/app.ts
index 1a0d8999..4943341f 100644
--- a/src/plugins/app.ts
+++ b/src/plugins/app.ts
@@ -25,8 +25,8 @@ export function setupAppVersionNotification() {
const buildTime = await getHtmlBuildTime();
- // If build time hasn't changed, no update is needed
- if (buildTime === BUILD_TIME) {
+ // If failed to get build time or build time hasn't changed, no update is needed.
+ if (!buildTime || buildTime === BUILD_TIME) {
return;
}
@@ -88,16 +88,22 @@ export function setupAppVersionNotification() {
}
}
-async function getHtmlBuildTime() {
+async function getHtmlBuildTime(): Promise
{
const baseUrl = import.meta.env.VITE_BASE_URL || '/';
- const res = await fetch(`${baseUrl}index.html?time=${Date.now()}`);
+ try {
+ const res = await fetch(`${baseUrl}index.html?time=${Date.now()}`);
- const html = await res.text();
+ if (!res.ok) {
+ console.error('getHtmlBuildTime error:', res.status, res.statusText);
+ return null;
+ }
- const match = html.match(//);
-
- const buildTime = match?.[1] || '';
-
- return buildTime;
+ const html = await res.text();
+ const match = html.match(//);
+ return match?.[1] || null;
+ } catch (error) {
+ console.error('getHtmlBuildTime error:', error);
+ return null;
+ }
}
diff --git a/src/router/elegant/helper.ts b/src/router/elegant/helper.ts
deleted file mode 100644
index 023a4d66..00000000
--- a/src/router/elegant/helper.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-/* eslint-disable no-console */
-/** 后台返回的路由动态生成name 解决缓存问题 感谢 @fourteendp 详见 https://github.com/vbenjs/vue-vben-admin/issues/3927 */
-import type { Component } from 'vue';
-import { defineComponent, h } from 'vue';
-
-interface Options {
- name?: string;
-}
-
-export type RouteComponentLoader = () => Promise;
-
-/**
- * 作用相当于给组件包了一层 & 设置name 解决keepAlive问题
- *
- * @param loader 导入的路由
- * @param options options
- * @returns components
- */
-export function createCustomNameComponent(
- loader: RouteComponentLoader,
- options: Options = {}
-): () => Promise {
- const { name } = options;
- let component: Component | null = null;
-
- const load = async () => {
- try {
- const { default: loadedComponent } = await loader();
- component = loadedComponent;
- } catch (error) {
- console.error(`Cannot resolve component ${name}, error:`, error);
- }
- };
-
- return async () => {
- if (!component) {
- await load();
- }
-
- return Promise.resolve(
- defineComponent({
- name,
- render() {
- return h(component as Component);
- }
- })
- );
- };
-}
diff --git a/src/service/api/system/menu.ts b/src/service/api/system/menu.ts
index 29853dc8..926b1ad7 100644
--- a/src/service/api/system/menu.ts
+++ b/src/service/api/system/menu.ts
@@ -59,3 +59,11 @@ export function fetchGetTenantPackageMenuTreeSelect(packageId: CommonType.IdType
method: 'get'
});
}
+
+// 级联删除菜单
+export function fetchCascadeDeleteMenu(menuIds: CommonType.IdType[]) {
+ return request({
+ url: `/system/menu/cascade/${menuIds.join(',')}`,
+ method: 'delete'
+ });
+}
diff --git a/src/service/api/system/oss.ts b/src/service/api/system/oss.ts
index 2a1d1c52..23a6e07d 100644
--- a/src/service/api/system/oss.ts
+++ b/src/service/api/system/oss.ts
@@ -1,4 +1,3 @@
-import type { AxiosRequestConfig, GenericAbortSignal } from 'axios';
import { request } from '@/service/request';
/** 获取文件管理列表 */
@@ -18,36 +17,10 @@ export function fetchBatchDeleteOss(ossIds: CommonType.IdType[]) {
});
}
-/** Axios上传进度事件 */
-export type AxiosProgressEvent = AxiosRequestConfig['onUploadProgress'];
-
-/** 默认上传结果 */
-export interface UploadResult {
- url: string;
- fileName: string;
- ossId: string;
-}
-
-export interface UploadApiOptions {
- onUploadProgress?: AxiosProgressEvent;
- signal?: GenericAbortSignal;
-}
-
-/** 上传文件接口 */
-export function uploadApi(file: File | Blob, options?: UploadApiOptions) {
- const { onUploadProgress, signal } = options ?? {};
-
- const formData = new FormData();
- formData.append('file', file);
-
- return request({
- url: '/resource/oss/upload',
- method: 'post',
- data: formData,
- onUploadProgress,
- headers: {
- 'Content-Type': 'multipart/form-data'
- },
- signal
+// 查询OSS对象基于id串
+export function fetchGetOssListByIds(ossIds: CommonType.IdType[]) {
+ return request({
+ url: `/resource/oss/listByIds/${ossIds.join(',')}`,
+ method: 'get'
});
}
diff --git a/src/service/api/system/role.ts b/src/service/api/system/role.ts
index f1da7745..378f658e 100644
--- a/src/service/api/system/role.ts
+++ b/src/service/api/system/role.ts
@@ -36,6 +36,15 @@ export function fetchUpdateRoleStatus(data: Api.System.RoleOperateParams) {
});
}
+/** 修改角色数据权限 */
+export function fetchUpdateRoleDataScope(data: Api.System.RoleOperateParams) {
+ return request({
+ url: '/system/role/dataScope',
+ method: 'put',
+ data
+ });
+}
+
/** 批量删除角色信息 */
export function fetchBatchDeleteRole(roleIds: CommonType.IdType[]) {
return request({
diff --git a/src/store/modules/auth/index.ts b/src/store/modules/auth/index.ts
index 02f4f66d..d6092298 100644
--- a/src/store/modules/auth/index.ts
+++ b/src/store/modules/auth/index.ts
@@ -12,6 +12,7 @@ import { clearAuthStorage, getToken } from './shared';
export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
const route = useRoute();
+ const authStore = useAuthStore();
const routeStore = useRouteStore();
const tabStore = useTabStore();
const { toLogin, redirectFromLogin } = useRouterPush(false);
@@ -37,8 +38,6 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
/** Reset auth store */
async function resetStore() {
- const authStore = useAuthStore();
-
recordUserId();
clearAuthStorage();
diff --git a/src/store/modules/dict/index.ts b/src/store/modules/dict/index.ts
index da19ca55..c105fc01 100644
--- a/src/store/modules/dict/index.ts
+++ b/src/store/modules/dict/index.ts
@@ -1,11 +1,17 @@
import { ref } from 'vue';
import { defineStore } from 'pinia';
+import { $t } from '@/locales';
export const useDictStore = defineStore('dict', () => {
const dictData = ref<{ [key: string]: Api.System.DictData[] }>({});
const getDict = (key: string) => {
- return dictData.value[key];
+ return dictData.value[key]?.map(item => ({
+ ...item,
+ dictLabel: item.dictLabel?.startsWith(`dict.${item.dictType}.`)
+ ? $t(item.dictLabel as App.I18n.I18nKey)
+ : item.dictLabel
+ }));
};
const setDict = (key: string, dict: Api.System.DictData[]) => {
diff --git a/src/store/modules/notice/index.ts b/src/store/modules/notice/index.ts
index f2904a09..8af29067 100644
--- a/src/store/modules/notice/index.ts
+++ b/src/store/modules/notice/index.ts
@@ -1,5 +1,6 @@
import { reactive } from 'vue';
import { defineStore } from 'pinia';
+import { SetupStoreId } from '@/enum';
interface NoticeItem {
title?: string;
@@ -8,7 +9,7 @@ interface NoticeItem {
time: string;
}
-export const useNoticeStore = defineStore('notice', () => {
+export const useNoticeStore = defineStore(SetupStoreId.Notice, () => {
const state = reactive({
notices: [] as NoticeItem[]
});
@@ -21,6 +22,10 @@ export const useNoticeStore = defineStore('notice', () => {
state.notices.splice(state.notices.indexOf(notice), 1);
};
+ const readNotice = (notice: NoticeItem) => {
+ state.notices[state.notices.indexOf(notice)].read = true;
+ };
+
// 实现全部已读
const readAll = () => {
state.notices.forEach((item: any) => {
@@ -31,10 +36,12 @@ export const useNoticeStore = defineStore('notice', () => {
const clearNotice = () => {
state.notices = [];
};
+
return {
state,
addNotice,
removeNotice,
+ readNotice,
readAll,
clearNotice
};
diff --git a/src/store/modules/route/index.ts b/src/store/modules/route/index.ts
index 6edff7ea..d45391bb 100644
--- a/src/store/modules/route/index.ts
+++ b/src/store/modules/route/index.ts
@@ -103,6 +103,9 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
// eslint-disable-next-line complexity
function parseRouter(route: ElegantConstRoute, parent?: ElegantConstRoute) {
route.meta = route.meta ? route.meta : { title: route.name };
+ if (route.meta.title.startsWith('route.')) {
+ route.meta.i18nKey = route.meta.title as App.I18n.I18nKey;
+ }
const isLayout = route.component === 'Layout';
const isFramePage = route.component === 'FrameView';
const isParentLayout = route.component === 'ParentView';
diff --git a/src/store/modules/tab/index.ts b/src/store/modules/tab/index.ts
index 78aaac69..5f2e0ffc 100644
--- a/src/store/modules/tab/index.ts
+++ b/src/store/modules/tab/index.ts
@@ -100,7 +100,9 @@ export const useTabStore = defineStore(SetupStoreId.Tab, () => {
const removedTabRouteKey = tabs.value[removeTabIndex].routeKey;
const isRemoveActiveTab = activeTabId.value === tabId;
- const nextTab = tabs.value[removeTabIndex + 1] || homeTab.value;
+
+ // if remove the last tab, then switch to the second last tab
+ const nextTab = tabs.value[removeTabIndex + 1] || tabs.value[removeTabIndex - 1] || homeTab.value;
// remove tab
tabs.value.splice(removeTabIndex, 1);
diff --git a/src/theme/settings.ts b/src/theme/settings.ts
index 57bc26d3..bb8b9bb9 100644
--- a/src/theme/settings.ts
+++ b/src/theme/settings.ts
@@ -30,6 +30,9 @@ export const themeSettings: App.Theme.ThemeSetting = {
},
multilingual: {
visible: true
+ },
+ globalSearch: {
+ visible: true
}
},
tab: {
@@ -57,6 +60,14 @@ export const themeSettings: App.Theme.ThemeSetting = {
visible: import.meta.env.VITE_WATERMARK === 'Y',
text: 'RuoYi-Vue-Plus'
},
+ table: {
+ bordered: true,
+ bottomBordered: true,
+ singleColumn: false,
+ singleLine: true,
+ size: 'small',
+ striped: false
+ },
tokens: {
light: {
colors: {
diff --git a/src/typings/api/monitor.api.d.ts b/src/typings/api/monitor.api.d.ts
index acec1ce6..06bc1ebe 100644
--- a/src/typings/api/monitor.api.d.ts
+++ b/src/typings/api/monitor.api.d.ts
@@ -10,6 +10,9 @@ declare namespace Api {
* backend api module: "monitor"
*/
namespace Monitor {
+ /** 业务操作类型 */
+ type BusinessType = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
+
/** oper log */
type OperLog = Common.CommonRecord<{
/** 日志主键 */
@@ -19,13 +22,13 @@ declare namespace Api {
/** 系统模块 */
title: string;
/** 操作类型 */
- businessType: number;
+ businessType: Monitor.BusinessType;
/** 方法名称 */
method: string;
/** 请求方式 */
requestMethod: string;
/** 操作类别 */
- operatorType: number;
+ operatorType: string;
/** 操作人员 */
operName: string;
/** 部门名称 */
@@ -41,7 +44,7 @@ declare namespace Api {
/** 返回参数 */
jsonResult: string;
/** 操作状态 */
- status: number;
+ status: Common.EnableStatus;
/** 错误消息 */
errorMsg: string;
/** 操作时间 */
@@ -70,7 +73,7 @@ declare namespace Api {
/** 客户端 */
clientKey: string;
/** 设备类型 */
- deviceType: string;
+ deviceType: System.DeviceType;
/** 登录IP地址 */
ipaddr: string;
/** 登录地点 */
@@ -80,7 +83,7 @@ declare namespace Api {
/** 操作系统 */
os: string;
/** 登录状态(0成功 1失败) */
- status: string;
+ status: Common.EnableStatus;
/** 提示消息 */
msg: string;
/** 访问时间 */
@@ -149,7 +152,7 @@ declare namespace Api {
/** 所在部门 */
deptName: string;
/** 设备类型 */
- deviceType: string;
+ deviceType: System.DeviceType;
/** 登录时间 */
loginTime: number;
/** 令牌ID */
diff --git a/src/typings/api/system.api.d.ts b/src/typings/api/system.api.d.ts
index 23e5c82f..d91b0fef 100644
--- a/src/typings/api/system.api.d.ts
+++ b/src/typings/api/system.api.d.ts
@@ -34,7 +34,7 @@ declare namespace Api {
/** 显示顺序 */
roleSort: number;
/** 角色状态(0正常 1停用) */
- status: string;
+ status: Common.EnableStatus;
/** 是否管理员 */
superAdmin: boolean;
}>;
@@ -115,7 +115,7 @@ declare namespace Api {
/** 密码 */
password: string;
/** 帐号状态(0正常 1停用) */
- status: string;
+ status: Common.EnableStatus;
/** 最后登录IP */
loginIp: string;
/** 最后登录时间 */
@@ -356,7 +356,7 @@ declare namespace Api {
/** 字典键值 */
dictValue: string;
/** 是否默认(Y是 N否) */
- isDefault: string;
+ isDefault: Common.YesOrNoStatus;
/** 表格回显样式 */
listClass: NaiveUI.ThemeColor;
/** 备注 */
@@ -402,7 +402,7 @@ declare namespace Api {
/** 邮箱 */
email: string;
/** 部门状态(0正常 1停用) */
- status: string;
+ status: Common.EnableStatus;
/** 子部门 */
children: Dept[];
}>;
@@ -440,7 +440,7 @@ declare namespace Api {
/** 显示顺序 */
postSort: number;
/** 状态(0正常 1停用) */
- status: string;
+ status: Common.EnableStatus;
/** 备注 */
remark: string;
}>;
@@ -476,7 +476,7 @@ declare namespace Api {
/** 参数键值 */
configValue: string;
/** 是否内置 */
- configType: string;
+ configType: Common.YesOrNoStatus;
/** 备注 */
remark: string;
}>;
@@ -523,7 +523,7 @@ declare namespace Api {
/** 用户数量(-1不限制) */
accountCount: number;
/** 租户状态(0正常 1停用) */
- status: string;
+ status: Common.EnableStatus;
/** 删除标志(0代表存在 1代表删除) */
delFlag: string;
}>;
@@ -577,7 +577,7 @@ declare namespace Api {
/** 菜单树选择项是否关联显示 */
menuCheckStrictly: boolean;
/** 状态(0正常 1停用) */
- status: string;
+ status: Common.EnableStatus;
/** 删除标志(0代表存在 1代表删除) */
delFlag: string;
}>;
@@ -602,6 +602,9 @@ declare namespace Api {
/** tenant package select list */
type TenantPackageSelectList = Common.CommonRecord>;
+ /** 通知公告类型 */
+ type NoticeType = '1' | '2';
+
/** notice */
type Notice = Common.CommonRecord<{
/** 公告ID */
@@ -611,11 +614,11 @@ declare namespace Api {
/** 公告标题 */
noticeTitle: string;
/** 公告类型 */
- noticeType: string;
+ noticeType: System.NoticeType;
/** 公告内容 */
noticeContent: string;
/** 公告状态 */
- status: string;
+ status: Common.EnableStatus;
/** 创建者 */
createByName: string;
/** 备注 */
@@ -635,6 +638,12 @@ declare namespace Api {
/** notice list */
type NoticeList = Api.Common.PaginatingQueryRecord;
+ /** 授权类型 */
+ type GrantType = 'password' | 'sms' | 'password' | 'email' | 'xcx' | 'social';
+
+ /** 设备类型 */
+ type DeviceType = 'pc' | 'android' | 'ios' | 'xcx';
+
/** client */
type Client = Common.CommonRecord<{
/** id */
@@ -646,17 +655,17 @@ declare namespace Api {
/** 客户端秘钥 */
clientSecret: string;
/** 授权类型 */
- grantType: string;
+ grantType: System.GrantType;
/** 授权类型列表 */
- grantTypeList: string[];
+ grantTypeList: System.GrantType[];
/** 设备类型 */
- deviceType: string;
+ deviceType: System.DeviceType;
/** token活跃超时时间 */
activeTimeout: number;
/** token固定超时 */
timeout: number;
/** 状态 */
- status: string;
+ status: Common.EnableStatus;
/** 删除标志(0代表存在 1代表删除) */
delFlag: string;
}>;
@@ -758,13 +767,13 @@ declare namespace Api {
/** 自定义域名 */
domain: string;
/** 是否https(Y=是,N=否) */
- isHttps: Api.Common.YesOrNoStatus;
+ isHttps: Common.YesOrNoStatus;
/** 域 */
region: string;
/** 桶权限类型 */
- accessPolicy: Api.System.OssAccessPolicy;
+ accessPolicy: System.OssAccessPolicy;
/** 是否默认(0=是,1=否) */
- status: Api.Common.EnableStatus;
+ status: Common.EnableStatus;
/** 扩展字段 */
ext1: string;
/** 备注 */
diff --git a/src/typings/app.d.ts b/src/typings/app.d.ts
index b37b9083..99669476 100644
--- a/src/typings/app.d.ts
+++ b/src/typings/app.d.ts
@@ -58,6 +58,10 @@ declare namespace App {
/** Whether to show the multilingual */
visible: boolean;
};
+ globalSearch: {
+ /** Whether to show the GlobalSearch */
+ visible: boolean;
+ };
};
/** Tab */
tab: {
@@ -109,6 +113,20 @@ declare namespace App {
/** Watermark text */
text: string;
};
+ table: {
+ /** Whether to show the table border */
+ bordered: boolean;
+ /** Whether to show the table bottom border */
+ bottomBordered: boolean;
+ /** Whether to show the table single column */
+ singleColumn: boolean;
+ /** Whether to show the table single line */
+ singleLine: boolean;
+ /** Whether to show the table size */
+ size: UnionKey.ThemeTableSize;
+ /** Whether to show the table striped */
+ striped: boolean;
+ };
/** define some theme settings tokens, will transform to css variables */
tokens: {
light: ThemeSettingToken;
@@ -401,6 +419,9 @@ declare namespace App {
multilingual: {
visible: string;
};
+ globalSearch: {
+ visible: string;
+ };
};
tab: {
visible: string;
@@ -426,6 +447,15 @@ declare namespace App {
visible: string;
text: string;
};
+ tablePropsTitle: string;
+ table: {
+ size: { title: string } & Record;
+ bordered: string;
+ bottomBordered: string;
+ singleColumn: string;
+ singleLine: string;
+ striped: string;
+ };
themeDrawerTitle: string;
pageFunTitle: string;
resetCacheStrategy: { title: string } & Record;
@@ -437,6 +467,7 @@ declare namespace App {
};
};
route: Record;
+ dict: Record>;
page: {
common: {
id: string;
@@ -680,6 +711,7 @@ declare namespace App {
buttonPermissionList: string;
emptyMenu: string;
menuDetail: string;
+ cascadeDeleteContent: string;
iconifyTip: string;
isFrameTip: string;
isCacheTip: string;
@@ -691,6 +723,7 @@ declare namespace App {
form: {
parentId: FormMsg;
menuType: FormMsg;
+ menuIds: FormMsg;
icon: FormMsg;
menuName: FormMsg;
orderNum: FormMsg;
@@ -717,6 +750,7 @@ declare namespace App {
addMenu: string;
addChildMenu: string;
editMenu: string;
+ cascadeDelete: string;
};
notice: {
title: string;
diff --git a/src/typings/components.d.ts b/src/typings/components.d.ts
index ddee2f6e..586f01a9 100644
--- a/src/typings/components.d.ts
+++ b/src/typings/components.d.ts
@@ -15,6 +15,7 @@ declare module 'vue' {
copy: typeof import('./../components/custom/dept-tree-select copy.vue')['default']
CountTo: typeof import('./../components/custom/count-to.vue')['default']
DarkModeContainer: typeof import('./../components/common/dark-mode-container.vue')['default']
+ DataTable: typeof import('./../components/common/data-table.vue')['default']
DeptTree: typeof import('./../components/custom/dept-tree.vue')['default']
DeptTreeSelect: typeof import('./../components/custom/dept-tree-select.vue')['default']
DictRadio: typeof import('./../components/custom/dict-radio.vue')['default']
@@ -64,6 +65,7 @@ declare module 'vue' {
NA: typeof import('naive-ui')['NA']
NAlert: typeof import('naive-ui')['NAlert']
NAvatar: typeof import('naive-ui')['NAvatar']
+ NBadge: typeof import('naive-ui')['NBadge']
NBreadcrumb: typeof import('naive-ui')['NBreadcrumb']
NBreadcrumbItem: typeof import('naive-ui')['NBreadcrumbItem']
NButton: typeof import('naive-ui')['NButton']
@@ -84,6 +86,7 @@ declare module 'vue' {
NDrawerContent: typeof import('naive-ui')['NDrawerContent']
NDropdown: typeof import('naive-ui')['NDropdown']
NDynamicInput: typeof import('naive-ui')['NDynamicInput']
+ NEllipsis: typeof import('naive-ui')['NEllipsis']
NEmpty: typeof import('naive-ui')['NEmpty']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem']
@@ -130,6 +133,7 @@ declare module 'vue' {
NUpload: typeof import('naive-ui')['NUpload']
NUploadDragger: typeof import('naive-ui')['NUploadDragger']
NWatermark: typeof import('naive-ui')['NWatermark']
+ OssUpload: typeof import('./../components/custom/oss-upload.vue')['default']
PinToggler: typeof import('./../components/common/pin-toggler.vue')['default']
PostSelect: typeof import('./../components/custom/post-select.vue')['default']
ReloadButton: typeof import('./../components/common/reload-button.vue')['default']
diff --git a/src/typings/union-key.d.ts b/src/typings/union-key.d.ts
index a783294b..dc27a701 100644
--- a/src/typings/union-key.d.ts
+++ b/src/typings/union-key.d.ts
@@ -51,6 +51,15 @@ declare namespace UnionKey {
*/
type ThemeTabMode = import('@sa/materials').PageTabMode;
+ /**
+ * The table size
+ *
+ * - small: small size
+ * - medium: medium size
+ * - large: large size
+ */
+ type ThemeTableSize = 'small' | 'medium' | 'large';
+
/** Unocss animate key */
type UnoCssAnimateKey =
| 'pulse'
diff --git a/src/utils/common.ts b/src/utils/common.ts
index 22ac43e7..b5489f37 100644
--- a/src/utils/common.ts
+++ b/src/utils/common.ts
@@ -1,3 +1,4 @@
+import { AcceptType } from '@/enum/business';
import { $t } from '@/locales';
/**
* Transform record to option
@@ -87,8 +88,7 @@ export function isNull(value: any) {
/** 判断是否为图片类型 */
export function isImage(suffix: string) {
- const imgSuffixList = ['.jpg', '.jpeg', '.png', '.gif', '.webp'];
- return imgSuffixList.includes(suffix.toLowerCase());
+ return AcceptType.Image.split(',').includes(suffix.toLowerCase());
}
/**
diff --git a/src/utils/format.ts b/src/utils/icon-tag-format.ts
similarity index 100%
rename from src/utils/format.ts
rename to src/utils/icon-tag-format.ts
diff --git a/src/views/_builtin/user-center/modules/online-table.vue b/src/views/_builtin/user-center/modules/online-table.vue
index d5d4c8af..34690301 100644
--- a/src/views/_builtin/user-center/modules/online-table.vue
+++ b/src/views/_builtin/user-center/modules/online-table.vue
@@ -4,7 +4,7 @@ import { useLoading } from '@sa/hooks';
import { fetchForceLogout, fetchGetOnlineDeviceList } from '@/service/api/monitor';
import { useAppStore } from '@/store/modules/app';
import { useTable } from '@/hooks/common/table';
-import { getBrowserIcon, getOsIcon } from '@/utils/format';
+import { getBrowserIcon, getOsIcon } from '@/utils/icon-tag-format';
import { $t } from '@/locales';
import ButtonIcon from '@/components/custom/button-icon.vue';
import SvgIcon from '@/components/custom/svg-icon.vue';
@@ -16,12 +16,8 @@ defineOptions({
const appStore = useAppStore();
const { loading: btnLoading, startLoading: startBtnLoading, endLoading: endBtnLoading } = useLoading(false);
-const { columns, data, loading, mobilePagination, getData } = useTable({
+const { columns, data, loading, getData } = useTable({
apiFn: fetchGetOnlineDeviceList,
- apiParams: {
- pageNum: 1,
- pageSize: 15
- },
columns: () => [
{ title: '用户名', key: 'userName', align: 'center', minWidth: 120 },
{ title: 'IP地址', key: 'ipaddr', align: 'center', minWidth: 120 },
@@ -109,7 +105,6 @@ async function forceLogout(tokenId: string) {
:loading="loading"
remote
:row-key="row => row.noticeId"
- :pagination="mobilePagination"
class="h-full"
/>
diff --git a/src/views/demo/demo/index.vue b/src/views/demo/demo/index.vue
index fcc883fc..b9818f03 100644
--- a/src/views/demo/demo/index.vue
+++ b/src/views/demo/demo/index.vue
@@ -184,11 +184,10 @@ async function handleExport() {
@refresh="getData"
/>
- ;
+type RuleKey = Extract;
const rules: Record = {
id: createRequiredRule('主键不能为空'),
deptId: createRequiredRule('部门不能为空'),
userId: createRequiredRule('用户不能为空'),
+ orderNum: createRequiredRule('排序号不能为空'),
testKey: createRequiredRule('key 键不能为空'),
value: createRequiredRule('值不能为空')
};
@@ -124,7 +125,7 @@ watch(visible, () => {
-
+
diff --git a/src/views/demo/tree/index.vue b/src/views/demo/tree/index.vue
index ac748e67..44866eab 100644
--- a/src/views/demo/tree/index.vue
+++ b/src/views/demo/tree/index.vue
@@ -209,12 +209,11 @@ function handleExport() {
-
-import { getBrowserIcon, getOsIcon } from '@/utils/format';
+import { getBrowserIcon, getOsIcon } from '@/utils/icon-tag-format';
import { $t } from '@/locales';
defineOptions({
diff --git a/src/views/monitor/online/index.vue b/src/views/monitor/online/index.vue
index 80b3f7e1..df4bc061 100644
--- a/src/views/monitor/online/index.vue
+++ b/src/views/monitor/online/index.vue
@@ -5,7 +5,7 @@ import { useAppStore } from '@/store/modules/app';
import { useAuth } from '@/hooks/business/auth';
import { useTable } from '@/hooks/common/table';
import { useDict } from '@/hooks/business/dict';
-import { getBrowserIcon, getOsIcon } from '@/utils/format';
+import { getBrowserIcon, getOsIcon } from '@/utils/icon-tag-format';
import ButtonIcon from '@/components/custom/button-icon.vue';
import DictTag from '@/components/custom/dict-tag.vue';
import SvgIcon from '@/components/custom/svg-icon.vue';
diff --git a/src/views/monitor/operlog/modules/oper-log-view-drawer.vue b/src/views/monitor/operlog/modules/oper-log-view-drawer.vue
index 849364c0..c6baea2b 100644
--- a/src/views/monitor/operlog/modules/oper-log-view-drawer.vue
+++ b/src/views/monitor/operlog/modules/oper-log-view-drawer.vue
@@ -1,6 +1,6 @@
+
+
+
+
+
+
+
+
+
+
+ {{ $t('common.cancel') }}
+ {{ $t('common.confirm') }}
+
+
+
+
+
+
diff --git a/src/views/system/menu/modules/menu-operate-drawer.vue b/src/views/system/menu/modules/menu-operate-drawer.vue
index 65b57f05..e523ae44 100644
--- a/src/views/system/menu/modules/menu-operate-drawer.vue
+++ b/src/views/system/menu/modules/menu-operate-drawer.vue
@@ -83,8 +83,28 @@ const rules: Record = {
component: createRequiredRule($t('page.system.menu.form.component.invalid'))
};
-const isBtn = computed(() => model.menuType === 'F');
+// 是否为目录类型
+const isCatalog = computed(() => model.menuType === 'M');
+
+// 是否为菜单类型
const isMenu = computed(() => model.menuType === 'C');
+
+// 是否为按钮类型
+const isBtn = computed(() => model.menuType === 'F');
+
+// 外链类型
+const isExternalType = computed(() => model.isFrame === '0');
+
+// 内部类型
+const isInternalType = computed(() => model.isFrame === '1');
+
+// iframe类型
+const isIframeType = computed(() => model.isFrame === '2');
+
+// 本地图标类型
+const isLocalIcon = computed(() => iconType.value === '2');
+
+// 本地图标
const localIcons = getLocalMenuIcons();
const localIconOptions = localIcons.map(item => ({
label: () => (
@@ -102,7 +122,7 @@ function handleInitModel() {
if (props.operateType === 'edit' && props.rowData) {
Object.assign(model, props.rowData);
- if (isMenu.value && model.isFrame === '1') {
+ if (isMenu.value && isInternalType.value) {
model.component = model.component?.slice(0, -6);
}
iconType.value = model.icon?.startsWith('local-icon-') ? '2' : '1';
@@ -118,6 +138,44 @@ function closeDrawer() {
visible.value = false;
}
+// 处理路径
+function processPath(path: string | null | undefined): string {
+ return path?.startsWith('/') ? path.substring(1) : path || '';
+}
+
+// 处理组件
+function processComponent(component: string | null | undefined): string {
+ if (isCatalog.value && isInternalType.value) {
+ return 'Layout';
+ }
+ if (isIframeType.value || isExternalType.value) {
+ return 'FrameView';
+ }
+ if (isMenu.value && isInternalType.value) {
+ return component?.endsWith('/index') ? component : `${component || ''}/index`;
+ }
+ return component || '';
+}
+
+function processQueryParam(queryParam: string | null | undefined): string {
+ // 外链类型不需要查询参数
+ if (isExternalType.value) {
+ return '';
+ }
+
+ // 内部链接类型,处理动态参数
+ if (isInternalType.value && queryList.value.length) {
+ return JSON.stringify(Object.fromEntries(queryList.value.map(({ key, value }) => [key, value])));
+ }
+
+ // iframe类型,直接使用原始参数
+ if (isIframeType.value) {
+ return queryParam || '';
+ }
+
+ return '';
+}
+
async function handleSubmit() {
await validate();
@@ -133,77 +191,36 @@ async function handleSubmit() {
visible: menuVisible,
status,
perms,
- remark
+ remark,
+ component,
+ queryParam
} = model;
- let queryParam = model.queryParam;
- if (isFrame === '0') {
- queryParam = '';
- } else if (isFrame === '1' && queryList.value.length) {
- const queryObj: { [key: string]: string } = {};
- queryList.value.forEach(item => (queryObj[item.key] = item.value));
- queryParam = JSON.stringify(queryObj);
- }
-
- const path = model.path?.startsWith('/') ? model.path?.substring(1) : model.path;
-
- let component = model.component;
- if (isFrame === '1' && menuType === 'M') {
- component = 'Layout';
- } else if (isFrame === '2') {
- component = 'FrameView';
- } else if (isMenu.value && model.isFrame === '1') {
- component = component?.endsWith('/index') ? component : `${component}/index`;
- }
-
- // request
- if (props.operateType === 'add') {
- const { error } = await fetchCreateMenu({
- menuName,
- path,
- parentId,
- orderNum,
- queryParam,
- isFrame,
- isCache,
- menuType,
- visible: menuVisible,
- status,
- perms,
- icon,
- component,
- remark
- });
- if (error) {
- return;
- }
- window.$message?.success($t('common.addSuccess'));
- }
-
- if (props.operateType === 'edit') {
- const { error } = await fetchUpdateMenu({
- menuId,
- menuName,
- path,
- parentId,
- orderNum,
- queryParam,
- isFrame,
- isCache,
- menuType,
- visible: menuVisible,
- status,
- perms,
- icon,
- component,
- remark
- });
- if (error) {
- return;
- }
- window.$message?.success($t('common.updateSuccess'));
+ const payload = {
+ menuName,
+ path: processPath(model.path),
+ parentId,
+ orderNum,
+ queryParam: processQueryParam(queryParam),
+ isFrame,
+ isCache,
+ menuType,
+ visible: menuVisible,
+ status,
+ perms,
+ icon,
+ component: processComponent(component),
+ remark
+ };
+
+ const { error } =
+ props.operateType === 'add' ? await fetchCreateMenu(payload) : await fetchUpdateMenu({ ...payload, menuId });
+
+ if (error) {
+ return;
}
+ window.$message?.success($t(props.operateType === 'add' ? 'common.addSuccess' : 'common.updateSuccess'));
closeDrawer();
emit('submitted', menuType!);
}
@@ -224,7 +241,7 @@ function onCreate() {
-
+
@@ -238,12 +255,7 @@ function onCreate() {
:placeholder="$t('page.system.menu.form.parentId.required')"
/>
-
+
-
+
-
+
-
+
{{ $t('page.system.menu.icon') }}
-
+
+
+
+
-
-
+
+
+
+
+
+ {{ $t('page.system.menu.isFrame') }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ $t('page.system.menu.isCache') }}
+
+
+
+
+
+
+
+
- {{ model.isFrame !== '0' ? $t('page.system.menu.path') : $t('page.system.menu.externalPath') }}
+ {{ !isExternalType ? $t('page.system.menu.path') : $t('page.system.menu.externalPath') }}
-
+
@@ -313,13 +357,13 @@ function onCreate() {
-
-
-
-
- {{ $t('page.system.menu.isFrame') }}
-
-
-
-
-
-
-
-
-
-
-
-
- {{ $t('page.system.menu.isCache') }}
-
-
-
-
-
-
-
-
-
+
diff --git a/src/views/system/oss/modules/oss-upload-modal.vue b/src/views/system/oss/modules/oss-upload-modal.vue
index 3e440d93..54dd1053 100644
--- a/src/views/system/oss/modules/oss-upload-modal.vue
+++ b/src/views/system/oss/modules/oss-upload-modal.vue
@@ -1,11 +1,13 @@