19 Commits

Author SHA1 Message Date
3496852be1 Merge remote-tracking branch 'origin/dev' into v2.0
# Conflicts:
#	src/service/request/index.ts
#	src/views/_builtin/login/index.vue
#	src/views/system/dept/modules/dept-operate-drawer.vue
#	src/views/system/menu/modules/menu-operate-drawer.vue
#	src/views/system/post/index.vue
#	src/views/system/user/index.vue
#	src/views/tool/gen/modules/gen-table-operate-drawer.vue
2025-09-16 15:32:18 +08:00
4e27f3b5a5 fix(projects): 修复登录页面样式问题 2025-09-15 17:07:38 +08:00
e623b560e4 docs(other): 更新 cursor 规则 2025-09-15 11:40:20 +08:00
8aeb73627a fix(projects): 修复登录页面跳转问题 2025-09-13 13:59:51 +08:00
3f148a4e62 fix(projects): 修复消息通知字典值未处理问题 2025-09-12 10:04:36 +08:00
34ab7d5da2 fix(projects): 修复菜单默认图标问题 2025-09-11 11:45:52 +08:00
513dc31eaa feat(styles): 优化左侧树形结构样式 2025-09-11 10:28:43 +08:00
dc2fbbd556 fix(projects): 修复退出登录未清空消息列表问题 2025-09-11 10:21:11 +08:00
56fd5434ca optimize(projects): 字典状态使用枚举值 2025-09-11 10:16:20 +08:00
ad207255bb fix(projects): 修复菜单弹窗打开未清空默认值问题 2025-09-11 10:14:45 +08:00
3146c039f0 feat(projects): 用户列表新增头像展示 2025-09-11 10:09:35 +08:00
AN
d5bbc37dec fix(projects): 修复新增部门时不显示上级部门问题 2025-09-10 11:48:13 +08:00
AN
2f794c4b73 fix(projects): 修改代码生成功能模块名为驼峰时,路由错误问题 2025-09-10 10:40:23 +08:00
AN
378aa869bf style(components): 修改json预览组件样式问题 2025-09-08 13:51:28 +08:00
AN
4a4244b5c4 style(styles): 修复字体样式导致下划线不可见问题 2025-09-05 17:20:55 +08:00
AN
ecad1c3e78 optimize(components): 补充国际化 2025-09-05 14:31:23 +08:00
9ef0bd416e fix(utils): 修复请求工具响应解密问题 2025-09-04 14:57:12 +08:00
25ee32074a feat(projects): 路由兼容 activeMenu 选项 2025-09-02 15:10:59 +08:00
8412a8db16 feat(projects): 重构登录页面样式 2025-09-02 14:49:58 +08:00
20 changed files with 764 additions and 945 deletions

View File

@ -16,7 +16,7 @@
},
"playwright": {
"command": "npx",
"args": ["@playwright/mcp@0.0.29"]
"args": ["@playwright/mcp@latest"]
},
"mcp-server-time": {
"command": "uvx",
@ -26,14 +26,21 @@
"command": "npx",
"args": ["-y", "mcp-shrimp-task-manager"],
"env": {
"DATA_DIR": "D:/workspace/mcp-shrimp-task-manager/data",
"DATA_DIR": "D:/workspace/tools/mcp-shrimp-task-manager/data",
"TEMPLATES_USE": "en",
"ENABLE_GUI": "false"
"ENABLE_GUI": "true"
}
},
"mcp-deepwiki": {
"command": "npx",
"args": ["-y", "mcp-deepwiki@latest"]
},
"memory": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-memory"],
"env": {
"MEMORY_FILE_PATH": "D:/workspace/tools/server-memory/memory.json"
}
}
}
}

View File

@ -1,9 +1,6 @@
---
description:
globs:
alwaysApply: false
---
**# RIPER-5 + 多维度思维 + 代理执行协议 (v4.9.1 - MCP工具驱动版)**
**元指令:** 此协议旨在最大化你的战略规划与执行效率。你的核心任务是**指挥和利用MCP工具集**来驱动项目进展。严格遵守核心原则,利用 `mcp-shrimp-task-manager` 进行项目规划与追踪,使用 `deepwiki-mcp` 进行深度研究。主动管理 `/project_document` 作为知识库。**每轮主要响应后,调用 `mcp.feedback_enhanced` 进行交互或通知。**
@ -167,5 +164,4 @@ alwaysApply: false
* **极致效率:** AI应最大限度地减少手动干预让MCP工具处理所有可以自动化的工作。
* **战略聚焦:** 将AI的“思考”集中在无法被工具替代的领域战略决策、创新构想、复杂问题诊断 (`mcp.sequential_thinking`) 和最终质量把关。
* **无缝集成:** 期望AI能流畅地在不同MCP工具之间传递信息形成一个高度整合的自动化工作流。

View File

@ -15,6 +15,8 @@ VITE_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
VITE_APP_ENCRYPT=Y
# AES 加密头标识
VITE_HEADER_FLAG=encrypt-key
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
VITE_APP_RSA_PUBLIC_KEY='MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换

View File

@ -12,6 +12,8 @@ VITE_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
VITE_APP_ENCRYPT=Y
# AES 加密头标识
VITE_HEADER_FLAG=encrypt-key
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
VITE_APP_RSA_PUBLIC_KEY='MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换

View File

@ -12,6 +12,8 @@ VITE_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
# 接口加密功能开关(如需关闭 后端也必须对应关闭)
VITE_APP_ENCRYPT=Y
# AES 加密头标识
VITE_HEADER_FLAG=encrypt-key
# 接口加密传输 RSA 公钥与后端解密私钥对应 如更换需前后端一同更换
VITE_APP_RSA_PUBLIC_KEY='MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='
# 接口响应解密 RSA 私钥与后端加密公钥对应 如更换需前后端一同更换

View File

@ -31,7 +31,8 @@
"vue.server.hybridMode": true,
"files.exclude": { "/docs": true },
"search.exclude": {
"/docs": true
"/docs": true,
"**/dist/**": true
},
"cSpell.words": ["Axios", "tinymce"]
}

View File

@ -10,6 +10,7 @@ import org.apache.velocity.VelocityContext;
import org.dromara.common.core.utils.DateUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.json.utils.JsonUtils;
import org.dromara.common.mybatis.enums.DataBaseType;
import org.dromara.common.mybatis.helper.DataBaseHelper;
import org.dromara.generator.constant.GenConstants;
import org.dromara.generator.domain.GenTable;
@ -58,7 +59,7 @@ public class VelocityUtils {
velocityContext.put("functionName", StringUtils.isNotEmpty(functionName) ? functionName : "【请填写功能名称】");
velocityContext.put("ClassName", genTable.getClassName());
velocityContext.put("className", StringUtils.uncapitalize(genTable.getClassName()));
velocityContext.put("moduleName", genTable.getModuleName());
velocityContext.put("moduleName", StrUtil.toSymbolCase(genTable.getModuleName(), '-'));
velocityContext.put("BusinessName", StringUtils.capitalize(genTable.getBusinessName()));
velocityContext.put("businessName", genTable.getBusinessName());
velocityContext.put("business_name", StrUtil.toUnderlineCase(genTable.getBusinessName()));
@ -124,11 +125,12 @@ public class VelocityUtils {
templates.add("vm/java/serviceImpl.java.vm");
templates.add("vm/java/controller.java.vm");
templates.add("vm/xml/mapper.xml.vm");
if (DataBaseHelper.isOracle()) {
DataBaseType dataBaseType = DataBaseHelper.getDataBaseType();
if (dataBaseType.isOracle()) {
templates.add("vm/sql/oracle/sql.vm");
} else if (DataBaseHelper.isPostgerSql()) {
} else if (dataBaseType.isPostgreSql()) {
templates.add("vm/sql/postgres/sql.vm");
} else if (DataBaseHelper.isSqlServer()) {
} else if (dataBaseType.isSqlServer()) {
templates.add("vm/sql/sqlserver/sql.vm");
} else {
templates.add("vm/sql/sql.vm");
@ -163,7 +165,7 @@ public class VelocityUtils {
String javaPath = PROJECT_PATH + "/" + StringUtils.replace(packageName, ".", "/");
String mybatisPath = MYBATIS_PATH + "/" + moduleName;
String soybeanPath = "soy";
String soybeanModuleName = StrUtil.toSymbolCase(moduleName, '-');
if (template.contains("domain.java.vm")) {
fileName = StringUtils.format("{}/domain/{}.java", javaPath, className);
}
@ -186,17 +188,17 @@ public class VelocityUtils {
} else if (template.contains("sql.vm")) {
fileName = businessName + "Menu.sql";
} else if (template.contains("index.vue.vm")) {
fileName = StringUtils.format("{}/views/{}/{}/index.vue", soybeanPath, moduleName, StrUtil.toSymbolCase(businessName, '-'));
fileName = StringUtils.format("{}/views/{}/{}/index.vue", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'));
} else if (template.contains("index-tree.vue.vm")) {
fileName = StringUtils.format("{}/views/{}/{}/index.vue", soybeanPath, moduleName, StrUtil.toSymbolCase(businessName, '-'));
fileName = StringUtils.format("{}/views/{}/{}/index.vue", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'));
} else if (template.contains("api.d.ts.vm")) {
fileName = StringUtils.format("{}/typings/api/{}.{}.api.d.ts", soybeanPath, moduleName, StrUtil.toSymbolCase(businessName, '-'));
fileName = StringUtils.format("{}/typings/api/{}.{}.api.d.ts", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'));
} else if (template.contains("api.ts.vm")) {
fileName = StringUtils.format("{}/service/api/{}/{}.ts", soybeanPath, moduleName, StrUtil.toSymbolCase(businessName, '-'));
fileName = StringUtils.format("{}/service/api/{}/{}.ts", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'));
} else if (template.contains("search.vue.vm")) {
fileName = StringUtils.format("{}/views/{}/{}/modules/{}-search.vue", soybeanPath, moduleName, StrUtil.toSymbolCase(businessName, '-'), StrUtil.toSymbolCase(businessName, '-'));
fileName = StringUtils.format("{}/views/{}/{}/modules/{}-search.vue", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'), StrUtil.toSymbolCase(businessName, '-'));
} else if (template.contains("operate-drawer.vue.vm")) {
fileName = StringUtils.format("{}/views/{}/{}/modules/{}-operate-drawer.vue", soybeanPath, moduleName, StrUtil.toSymbolCase(businessName, '-'), StrUtil.toSymbolCase(businessName, '-'));
fileName = StringUtils.format("{}/views/{}/{}/modules/{}-operate-drawer.vue", soybeanPath, soybeanModuleName, StrUtil.toSymbolCase(businessName, '-'), StrUtil.toSymbolCase(businessName, '-'));
}
return fileName;
}

1539
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,7 @@
import { computed } from 'vue';
import hljs from 'highlight.js/lib/core';
import json from 'highlight.js/lib/languages/json';
import { twMerge } from 'tailwind-merge';
hljs.registerLanguage('json', json);
@ -10,15 +11,19 @@ defineOptions({
});
interface Props {
class?: string;
code?: string;
showLineNumbers?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
class: '',
code: '',
showLineNumbers: false
});
const DEFAULT_CLASS = 'max-h-500px';
/** 格式化JSON数据 */
const jsonData = computed<string>(() => {
if (!props.code) return '';
@ -33,9 +38,9 @@ const jsonData = computed<string>(() => {
</script>
<template>
<div class="json-preview">
<NCode :code="jsonData" :hljs="hljs" language="json" :show-line-numbers="showLineNumbers" />
</div>
<NScrollbar :class="twMerge(DEFAULT_CLASS, props.class)">
<NCode :code="jsonData" :hljs="hljs" language="json" :show-line-numbers="showLineNumbers" :word-wrap="true" />
</NScrollbar>
</template>
<style lang="scss">
@ -44,18 +49,4 @@ html[class='dark'] {
background-color: #7c7777;
}
}
.json-preview {
width: 100%;
max-height: 500px;
overflow-y: auto;
@include scrollbar();
.empty-data {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
color: #999;
font-size: 14px;
}
}
</style>

View File

@ -4,5 +4,6 @@ export enum SetupStoreId {
Auth = 'auth-store',
Route = 'route-store',
Tab = 'tab-store',
Notice = 'notice-store'
Notice = 'notice-store',
Dict = 'dict-store'
}

View File

@ -33,7 +33,7 @@ const toGitee = () => {
</NBadge>
</NButton>
</template>
消息
{{ $t('page.home.message') }}
</NTooltip>
</template>
<NCard

View File

@ -8,7 +8,7 @@ import { decrypt, encrypt } from '@/utils/jsencrypt';
import { getAuthorization, handleExpiredRequest, showErrorMsg } from './shared';
import type { RequestInstanceState } from './type';
const encryptHeader = 'encrypt-key';
const encryptHeader = import.meta.env.VITE_HEADER_FLAG || 'encrypt-key';
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
@ -25,23 +25,6 @@ export const request = createFlatRequest(
refreshTokenPromise: null
} as RequestInstanceState,
transform(response: AxiosResponse<App.Service.Response<any>>) {
if (import.meta.env.VITE_APP_ENCRYPT === 'Y') {
// 加密后的 AES 秘钥
const keyStr = response.headers[encryptHeader];
// 加密
if (keyStr && keyStr !== '') {
const data = String(response.data);
// 请求体 AES 解密
const base64Str = decrypt(keyStr);
// base64 解码 得到请求头的 AES 秘钥
const aesKey = decryptBase64(base64Str.toString());
// aesKey 解码 data
const decryptData = decryptWithAes(data, aesKey);
// 将结果 (得到的是 JSON 字符串) 转为 JSON
response.data = JSON.parse(decryptData);
}
}
// 二进制数据则直接返回
if (response.request.responseType === 'blob' || response.request.responseType === 'arraybuffer') {
return response.data;
@ -81,6 +64,14 @@ export const request = createFlatRequest(
isBackendSuccess(response) {
// when the backend response code is "0000"(default), it means the request is success
// to change this logic by yourself, you can modify the `VITE_SERVICE_SUCCESS_CODE` in `.env` file
if (import.meta.env.VITE_APP_ENCRYPT === 'Y' && response.headers[encryptHeader]) {
const keyStr = response.headers[encryptHeader];
const data = String(response.data);
const base64Str = decrypt(keyStr);
const aesKey = decryptBase64(base64Str.toString());
const decryptData = decryptWithAes(data, aesKey);
response.data = JSON.parse(decryptData);
}
return String(response.data.code) === import.meta.env.VITE_SERVICE_SUCCESS_CODE;
},
async onBackendFail(response, instance) {

View File

@ -8,6 +8,7 @@ import { localStg } from '@/utils/storage';
import { SetupStoreId } from '@/enum';
import { useRouteStore } from '../route';
import { useTabStore } from '../tab';
import useNoticeStore from '../notice';
import { clearAuthStorage, getToken } from './shared';
export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
@ -15,6 +16,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
const authStore = useAuthStore();
const routeStore = useRouteStore();
const tabStore = useTabStore();
const noticeStore = useNoticeStore();
const { toLogin, redirectFromLogin } = useRouterPush(false);
const { loading: loginLoading, startLoading, endLoading } = useLoading();
@ -48,6 +50,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
await toLogin();
}
noticeStore.clearNotice();
tabStore.cacheTabs();
routeStore.resetStore();
}

View File

@ -1,8 +1,9 @@
import { ref } from 'vue';
import { defineStore } from 'pinia';
import { $t } from '@/locales';
import { SetupStoreId } from '@/enum';
export const useDictStore = defineStore('dict', () => {
export const useDictStore = defineStore(SetupStoreId.Dict, () => {
const dictData = ref<{ [key: string]: Api.System.DictData[] }>({});
const getDict = (key: string) => {

View File

@ -127,7 +127,7 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
}
// @ts-expect-error no hidden field
route.meta.hideInMenu = route.hidden;
if (route.meta.hideInMenu && parent) {
if (route.meta.hideInMenu && parent && !route.meta.activeMenu) {
// @ts-expect-error parent.name is activeMenu type
route.meta.activeMenu = parent.name;
}

View File

@ -6,6 +6,7 @@ html,
body,
#app {
height: 100%;
font-family: inherit;
}
html {

View File

@ -43,7 +43,7 @@ export const themeSettings: App.Theme.ThemeSetting = {
fixedHeaderAndTab: true,
sider: {
inverted: false,
width: 220,
width: 230,
collapsedWidth: 64,
mixWidth: 90,
mixCollapsedWidth: 64,

View File

@ -114,6 +114,7 @@ declare namespace Env {
readonly VITE_DEVTOOLS_LAUNCH_EDITOR?: import('vite-plugin-vue-devtools').VitePluginVueDevToolsOptions['launchEditor'];
readonly VITE_APP_CLIENT_ID?: string;
readonly VITE_APP_ENCRYPT?: CommonType.YesOrNo;
readonly VITE_HEADER_FLAG?: string;
readonly VITE_APP_RSA_PUBLIC_KEY?: string;
readonly VITE_APP_RSA_PRIVATE_KEY?: string;
readonly VITE_APP_WEBSOCKET: CommonType.YesOrNo;

View File

@ -1,6 +1,7 @@
import { watch } from 'vue';
import { useEventSource } from '@vueuse/core';
import useNoticeStore from '@/store/modules/notice';
import { $t } from '@/locales';
import { localStg } from './storage';
// 初始化
@ -34,9 +35,14 @@ export const initSSE = (url: any) => {
read: false,
time: new Date().toLocaleString()
});
let content = data.value;
const noticeType = content.match(/\[dict\.(.*?)\]/)?.[1];
if (noticeType) {
content = content.replace(`dict.${noticeType}`, $t(`dict.${noticeType}` as App.I18n.I18nKey));
}
window.$notification?.create({
title: '消息',
content: data.value,
content,
type: 'success',
duration: 3000
});

View File

@ -39,8 +39,7 @@ const activeModule = computed(() => moduleMap[props.module || 'pwd-login']);
</script>
<template>
<!-- Copyright By https://github.com/Daymychen/art-design-pro/blob/main/src/components/core/views/login/LoginLeftView.vue -->
<div class="box-border size-full flex">
<div class="scroll box-border size-full flex">
<div class="relative box-border hidden h-full w-65vw overflow-hidden bg-primary-50 xl:block dark:bg-primary-900">
<div class="relative z-100 flex items-center pl-30px pt-30px">
<SystemLogo class="text-32px text-primary" />
@ -55,13 +54,13 @@ const activeModule = computed(() => moduleMap[props.module || 'pwd-login']);
</div>
<WaveBg />
</div>
<header class="relative h-full flex-1 xl:m-auto sm:!w-full">
<div class="relative z-100 block flex items-center pl-30px pt-30px xl:hidden">
<SystemLogo class="text-32px text-primary" />
<h3 class="ml-10px text-20px font-400">{{ $t('system.title') }}</h3>
</div>
<div class="position-fixed right-30px top-24px z-100 flex items-center justify-end">
<div class="ml-15px inline-block flex cursor-pointer select-none p-5px">
<div class="relative h-full flex-1 xl:m-auto sm:!w-full">
<header class="flex-y-center justify-between px-30px pt-30px xl:justify-end">
<div class="relative z-100 block flex items-center xl:hidden">
<SystemLogo class="text-32px text-primary" />
<h3 class="ml-10px text-20px font-400">{{ $t('system.title') }}</h3>
</div>
<div class="flex items-center justify-end">
<ThemeSchemaSwitch
:theme-schema="themeStore.themeScheme"
:show-tooltip="false"
@ -77,14 +76,32 @@ const activeModule = computed(() => moduleMap[props.module || 'pwd-login']);
@change-lang="appStore.changeLocale"
/>
</div>
</div>
<main class="absolute inset-0 m-auto h-630px max-w-450px w-full overflow-hidden rounded-5px bg-cover px-24px">
</header>
<main
class="m-auto mt-10% h-630px max-w-450px w-full rounded-5px bg-cover px-24px xl:absolute xl:inset-0 lg:mt-15% xl:mt-auto"
>
<Transition :name="themeStore.page.animateMode" mode="out-in" appear>
<component :is="activeModule.component" />
</Transition>
</main>
</header>
</div>
</div>
</template>
<style scoped></style>
<style scoped>
.scroll {
overflow: auto;
}
.scroll::-webkit-scrollbar {
display: none;
}
.scroll {
-ms-overflow-style: none;
}
.scroll {
scrollbar-width: none;
}
</style>