12 Commits

14 changed files with 458 additions and 393 deletions

View File

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

View File

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

View File

@ -220,7 +220,7 @@ const events = computed(() => {
<style lang="scss"> <style lang="scss">
.tox.tox-silver-sink.tox-tinymce-aux { .tox.tox-silver-sink.tox-tinymce-aux {
/** 该样式默认为1300的zIndex */ /** 该样式默认为1300的zIndex */
z-index: 2025; z-index: 2025 !important;
} }
.app-tinymce { .app-tinymce {

681
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -43,3 +43,10 @@ export function fetchGetPostSelect(deptId?: CommonType.IdType, postIds?: CommonT
params: { postIds, deptId } params: { postIds, deptId }
}); });
} }
/** 获取部门选择框列表 */
export function fetchGetPostDeptSelect() {
return request<Api.Common.CommonTreeRecord>({
url: '/system/post/deptTree',
method: 'get'
});
}

View File

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

View File

@ -1,8 +1,9 @@
import { ref } from 'vue'; import { ref } from 'vue';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { $t } from '@/locales'; 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 dictData = ref<{ [key: string]: Api.System.DictData[] }>({});
const getDict = (key: string) => { const getDict = (key: string) => {

View File

@ -1,6 +1,7 @@
import { watch } from 'vue'; import { watch } from 'vue';
import { useEventSource } from '@vueuse/core'; import { useEventSource } from '@vueuse/core';
import useNoticeStore from '@/store/modules/notice'; import useNoticeStore from '@/store/modules/notice';
import { $t } from '@/locales';
import { localStg } from './storage'; import { localStg } from './storage';
// 初始化 // 初始化
@ -34,9 +35,14 @@ export const initSSE = (url: any) => {
read: false, read: false,
time: new Date().toLocaleString() 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({ window.$notification?.create({
title: '消息', title: '消息',
content: data.value, content,
type: 'success', type: 'success',
duration: 3000 duration: 3000
}); });

View File

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

View File

@ -38,6 +38,8 @@ const visible = defineModel<boolean>('visible', {
default: false default: false
}); });
const defaultIcon = import.meta.env.VITE_MENU_ICON;
const iconType = ref<Api.System.IconType>('1'); const iconType = ref<Api.System.IconType>('1');
const { formRef, validate, restoreValidation } = useNaiveForm(); const { formRef, validate, restoreValidation } = useNaiveForm();
const { createRequiredRule, createNumberRequiredRule } = useFormRules(); const { createRequiredRule, createNumberRequiredRule } = useFormRules();
@ -69,7 +71,7 @@ function createDefaultModel(): Model {
visible: '0', visible: '0',
status: '0', status: '0',
perms: '', perms: '',
icon: undefined, icon: defaultIcon,
remark: '' remark: ''
}; };
} }
@ -118,6 +120,7 @@ const localIconOptions = localIcons.map<SelectOption>(item => ({
function handleInitModel() { function handleInitModel() {
queryList.value = []; queryList.value = [];
iconType.value = '1';
Object.assign(model, createDefaultModel()); Object.assign(model, createDefaultModel());
if (props.operateType === 'edit' && props.rowData) { if (props.operateType === 'edit' && props.rowData) {
@ -208,7 +211,7 @@ async function handleSubmit() {
visible: menuVisible, visible: menuVisible,
status, status,
perms, perms,
icon, icon: icon || defaultIcon,
component: processComponent(component), component: processComponent(component),
remark remark
}; };

View File

@ -2,8 +2,7 @@
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { NButton, NDivider } from 'naive-ui'; import { NButton, NDivider } from 'naive-ui';
import { useLoading } from '@sa/hooks'; import { useLoading } from '@sa/hooks';
import { fetchBatchDeletePost, fetchGetPostList } from '@/service/api/system/post'; import { fetchBatchDeletePost, fetchGetPostDeptSelect, fetchGetPostList } from '@/service/api/system/post';
import { fetchGetDeptTree } from '@/service/api/system';
import { useAppStore } from '@/store/modules/app'; import { useAppStore } from '@/store/modules/app';
import { useAuth } from '@/hooks/business/auth'; import { useAuth } from '@/hooks/business/auth';
import { useDownload } from '@/hooks/business/download'; import { useDownload } from '@/hooks/business/download';
@ -191,16 +190,17 @@ const { loading: treeLoading, startLoading: startTreeLoading, endLoading: endTre
const deptPattern = ref<string>(); const deptPattern = ref<string>();
const deptData = ref<Api.Common.CommonTreeRecord>([]); const deptData = ref<Api.Common.CommonTreeRecord>([]);
const selectedKeys = ref<string[]>([]); const selectedKeys = ref<string[]>([]);
async function getTreeData() {
async function getDeptOptions() {
// 加载
startTreeLoading(); startTreeLoading();
const { data: tree, error } = await fetchGetDeptTree(); const { data: tree, error } = await fetchGetPostDeptSelect();
if (!error) { if (!error) {
deptData.value = tree; deptData.value = tree;
} }
endTreeLoading(); endTreeLoading();
} }
getDeptOptions();
getTreeData();
function handleClickTree(keys: string[]) { function handleClickTree(keys: string[]) {
searchParams.belongDeptId = keys.length ? keys[0] : null; searchParams.belongDeptId = keys.length ? keys[0] : null;
@ -210,7 +210,7 @@ function handleClickTree(keys: string[]) {
function handleResetTreeData() { function handleResetTreeData() {
deptPattern.value = undefined; deptPattern.value = undefined;
getTreeData(); getDeptOptions();
} }
function handleResetSearch() { function handleResetSearch() {
@ -323,11 +323,11 @@ function handleResetSearch() {
} }
:deep(.n-tree-node) { :deep(.n-tree-node) {
height: 33px; height: 30px;
} }
:deep(.n-tree-node-switcher) { :deep(.n-tree-node-switcher) {
height: 33px; height: 30px;
} }
:deep(.n-tree-node-switcher__icon) { :deep(.n-tree-node-switcher__icon) {

View File

@ -1,6 +1,6 @@
<script setup lang="tsx"> <script setup lang="tsx">
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { NButton, NDivider } from 'naive-ui'; import { NAvatar, NButton, NDivider, NEllipsis } from 'naive-ui';
import { useBoolean, useLoading } from '@sa/hooks'; import { useBoolean, useLoading } from '@sa/hooks';
import { jsonClone } from '@sa/utils'; import { jsonClone } from '@sa/utils';
import { fetchBatchDeleteUser, fetchGetDeptTree, fetchGetUserList, fetchUpdateUserStatus } from '@/service/api/system'; import { fetchBatchDeleteUser, fetchGetDeptTree, fetchGetUserList, fetchUpdateUserStatus } from '@/service/api/system';
@ -12,6 +12,7 @@ import { useDownload } from '@/hooks/business/download';
import ButtonIcon from '@/components/custom/button-icon.vue'; import ButtonIcon from '@/components/custom/button-icon.vue';
import { $t } from '@/locales'; import { $t } from '@/locales';
import StatusSwitch from '@/components/custom/status-switch.vue'; import StatusSwitch from '@/components/custom/status-switch.vue';
import DictTag from '@/components/custom/dict-tag.vue';
import UserOperateDrawer from './modules/user-operate-drawer.vue'; import UserOperateDrawer from './modules/user-operate-drawer.vue';
import UserImportModal from './modules/user-import-modal.vue'; import UserImportModal from './modules/user-import-modal.vue';
import UserPasswordDrawer from './modules/user-password-drawer.vue'; import UserPasswordDrawer from './modules/user-password-drawer.vue';
@ -65,41 +66,64 @@ const {
key: 'index', key: 'index',
title: $t('common.index'), title: $t('common.index'),
align: 'center', align: 'center',
width: 64 width: 48
}, },
{ {
key: 'userName', key: 'userName',
title: $t('page.system.user.userName'), title: $t('page.system.user.userName'),
align: 'center', align: 'left',
minWidth: 120, width: 200,
ellipsis: true ellipsis: true,
render: row => {
return (
<div class="flex items-center justify-center gap-2">
<NAvatar src={row.avatar} class="bg-primary">
{row.avatar ? undefined : row.nickName.charAt(0)}
</NAvatar>
<div class="max-w-160px flex flex-col">
<NEllipsis>{row.userName}</NEllipsis>
<NEllipsis>{row.nickName}</NEllipsis>
</div>
</div>
);
}
}, },
{ {
key: 'nickName', key: 'sex',
title: $t('page.system.user.nickName'), title: $t('page.system.user.sex'),
align: 'center', align: 'center',
minWidth: 120, width: 80,
ellipsis: true ellipsis: true,
render(row) {
return <DictTag value={row.sex} dictCode="sys_user_sex" />;
}
}, },
{ {
key: 'deptName', key: 'deptName',
title: $t('page.system.user.deptName'), title: $t('page.system.user.deptName'),
align: 'center', align: 'center',
minWidth: 120, width: 120,
ellipsis: true
},
{
key: 'email',
title: $t('page.system.user.email'),
align: 'center',
width: 120,
ellipsis: true ellipsis: true
}, },
{ {
key: 'phonenumber', key: 'phonenumber',
title: $t('page.system.user.phonenumber'), title: $t('page.system.user.phonenumber'),
align: 'center', align: 'center',
minWidth: 120, width: 120,
ellipsis: true ellipsis: true
}, },
{ {
key: 'status', key: 'status',
title: $t('page.system.user.status'), title: $t('page.system.user.status'),
align: 'center', align: 'center',
minWidth: 80, width: 80,
render(row) { render(row) {
return ( return (
<StatusSwitch <StatusSwitch
@ -115,7 +139,7 @@ const {
key: 'createTime', key: 'createTime',
title: $t('page.system.user.createTime'), title: $t('page.system.user.createTime'),
align: 'center', align: 'center',
minWidth: 120 width: 120
}, },
{ {
key: 'operate', key: 'operate',
@ -341,7 +365,7 @@ function handleResetSearch() {
:data="data" :data="data"
size="small" size="small"
:flex-height="!appStore.isMobile" :flex-height="!appStore.isMobile"
:scroll-x="962" :scroll-x="1200"
:loading="loading" :loading="loading"
remote remote
:row-key="row => row.userId" :row-key="row => row.userId"
@ -391,11 +415,11 @@ function handleResetSearch() {
} }
:deep(.n-tree-node) { :deep(.n-tree-node) {
height: 25px; height: 30px;
} }
:deep(.n-tree-node-switcher) { :deep(.n-tree-node-switcher) {
height: 25px; height: 30px;
} }
:deep(.n-tree-node-switcher__icon) { :deep(.n-tree-node-switcher__icon) {