diff --git a/.env.development b/.env.development index c026485a..34c56c6d 100644 --- a/.env.development +++ b/.env.development @@ -1,5 +1,5 @@ # 应用端口 -VITE_APP_PORT=3000 +APP_PORT=3000 # 项目名称 VITE_APP_TITLE=vue3-element-admin @@ -8,9 +8,9 @@ VITE_APP_TITLE=vue3-element-admin VITE_API_BASE=/dev-api # 接口地址(开发代理转发目标,默认走线上) -VITE_PROXY_TARGET=https://api.youlai.tech # 线上 -# VITE_PROXY_TARGET=http://localhost:8000 # 本地 -# VITE_PROXY_TARGET=https://api.youlai.tech/v2 # 多租户线上 +# API_PROXY_TARGET=https://www.youlai.tech # 线上 +API_PROXY_TARGET=http://localhost:8000 # 本地 +# API_PROXY_TARGET=https://api.youlai.tech/v2 # 多租户线上 # Mock 开关 VITE_MOCK_ENABLED=false @@ -19,4 +19,7 @@ VITE_MOCK_ENABLED=false VITE_TENANT_ENABLED=false # AI 开关 -VITE_AI_ENABLED=true +VITE_AI_ENABLED=false + +# OIDC 登录入口,后端需启用相应配置 +VITE_OIDC_ENABLED=false diff --git a/.env.production b/.env.production index 648bf26b..3998e74d 100644 --- a/.env.production +++ b/.env.production @@ -9,3 +9,6 @@ VITE_TENANT_ENABLED=false # AI 辅助功能开关(true:开启 false:关闭,缺省关闭) VITE_AI_ENABLED=true + +# OIDC 登录入口,后端需启用相应配置 +VITE_OIDC_ENABLED=false diff --git a/.eslintrc-auto-import.json b/.eslintrc-auto-import.json deleted file mode 100644 index 0e8546f8..00000000 --- a/.eslintrc-auto-import.json +++ /dev/null @@ -1,316 +0,0 @@ -{ - "globals": { - "Component": true, - "ComponentPublicInstance": true, - "ComputedRef": true, - "EffectScope": true, - "ElMessage": true, - "ElMessageBox": true, - "ElNotification": true, - "InjectionKey": true, - "PropType": true, - "Ref": true, - "VNode": true, - "asyncComputed": true, - "autoResetRef": true, - "computed": true, - "computedAsync": true, - "computedEager": true, - "computedInject": true, - "computedWithControl": true, - "controlledComputed": true, - "controlledRef": true, - "createApp": true, - "createEventHook": true, - "createGlobalState": true, - "createInjectionState": true, - "createReactiveFn": true, - "createReusableTemplate": true, - "createSharedComposable": true, - "createTemplatePromise": true, - "createUnrefFn": true, - "customRef": true, - "debouncedRef": true, - "debouncedWatch": true, - "defineAsyncComponent": true, - "defineComponent": true, - "eagerComputed": true, - "effectScope": true, - "extendRef": true, - "getCurrentInstance": true, - "getCurrentScope": true, - "h": true, - "ignorableWatch": true, - "inject": true, - "isDefined": true, - "isProxy": true, - "isReactive": true, - "isReadonly": true, - "isRef": true, - "makeDestructurable": true, - "markRaw": true, - "nextTick": true, - "onActivated": true, - "onBeforeMount": true, - "onBeforeUnmount": true, - "onBeforeUpdate": true, - "onClickOutside": true, - "onDeactivated": true, - "onErrorCaptured": true, - "onKeyStroke": true, - "onLongPress": true, - "onMounted": true, - "onRenderTracked": true, - "onRenderTriggered": true, - "onScopeDispose": true, - "onServerPrefetch": true, - "onStartTyping": true, - "onUnmounted": true, - "onUpdated": true, - "pausableWatch": true, - "provide": true, - "reactify": true, - "reactifyObject": true, - "reactive": true, - "reactiveComputed": true, - "reactiveOmit": true, - "reactivePick": true, - "readonly": true, - "ref": true, - "refAutoReset": true, - "refDebounced": true, - "refDefault": true, - "refThrottled": true, - "refWithControl": true, - "resolveComponent": true, - "resolveRef": true, - "resolveUnref": true, - "shallowReactive": true, - "shallowReadonly": true, - "shallowRef": true, - "syncRef": true, - "syncRefs": true, - "templateRef": true, - "throttledRef": true, - "throttledWatch": true, - "toRaw": true, - "toReactive": true, - "toRef": true, - "toRefs": true, - "toValue": true, - "triggerRef": true, - "tryOnBeforeMount": true, - "tryOnBeforeUnmount": true, - "tryOnMounted": true, - "tryOnScopeDispose": true, - "tryOnUnmounted": true, - "unref": true, - "unrefElement": true, - "until": true, - "useActiveElement": true, - "useAnimate": true, - "useArrayDifference": true, - "useArrayEvery": true, - "useArrayFilter": true, - "useArrayFind": true, - "useArrayFindIndex": true, - "useArrayFindLast": true, - "useArrayIncludes": true, - "useArrayJoin": true, - "useArrayMap": true, - "useArrayReduce": true, - "useArraySome": true, - "useArrayUnique": true, - "useAsyncQueue": true, - "useAsyncState": true, - "useAttrs": true, - "useBase64": true, - "useBattery": true, - "useBluetooth": true, - "useBreakpoints": true, - "useBroadcastChannel": true, - "useBrowserLocation": true, - "useCached": true, - "useClipboard": true, - "useCloned": true, - "useColorMode": true, - "useConfirmDialog": true, - "useCounter": true, - "useCssModule": true, - "useCssVar": true, - "useCssVars": true, - "useCurrentElement": true, - "useCycleList": true, - "useDark": true, - "useDateFormat": true, - "useDebounce": true, - "useDebounceFn": true, - "useDebouncedRefHistory": true, - "useDeviceMotion": true, - "useDeviceOrientation": true, - "useDevicePixelRatio": true, - "useDevicesList": true, - "useDisplayMedia": true, - "useDocumentVisibility": true, - "useDraggable": true, - "useDropZone": true, - "useElementBounding": true, - "useElementByPoint": true, - "useElementHover": true, - "useElementSize": true, - "useElementVisibility": true, - "useEventBus": true, - "useEventListener": true, - "useEventSource": true, - "useEyeDropper": true, - "useFavicon": true, - "useFetch": true, - "useFileDialog": true, - "useFileSystemAccess": true, - "useFocus": true, - "useFocusWithin": true, - "useFps": true, - "useFullscreen": true, - "useGamepad": true, - "useGeolocation": true, - "useIdle": true, - "useImage": true, - "useInfiniteScroll": true, - "useIntersectionObserver": true, - "useInterval": true, - "useIntervalFn": true, - "useKeyModifier": true, - "useLastChanged": true, - "useLocalStorage": true, - "useMagicKeys": true, - "useManualRefHistory": true, - "useMediaControls": true, - "useMediaQuery": true, - "useMemoize": true, - "useMemory": true, - "useMounted": true, - "useMouse": true, - "useMouseInElement": true, - "useMousePressed": true, - "useMutationObserver": true, - "useNavigatorLanguage": true, - "useNetwork": true, - "useNow": true, - "useObjectUrl": true, - "useOffsetPagination": true, - "useOnline": true, - "usePageLeave": true, - "useParallax": true, - "useParentElement": true, - "usePerformanceObserver": true, - "usePermission": true, - "usePointer": true, - "usePointerLock": true, - "usePointerSwipe": true, - "usePreferredColorScheme": true, - "usePreferredContrast": true, - "usePreferredDark": true, - "usePreferredLanguages": true, - "usePreferredReducedMotion": true, - "usePrevious": true, - "useRafFn": true, - "useRefHistory": true, - "useResizeObserver": true, - "useScreenOrientation": true, - "useScreenSafeArea": true, - "useScriptTag": true, - "useScroll": true, - "useScrollLock": true, - "useSessionStorage": true, - "useShare": true, - "useSlots": true, - "useSorted": true, - "useSpeechRecognition": true, - "useSpeechSynthesis": true, - "useStepper": true, - "useStorage": true, - "useStorageAsync": true, - "useStyleTag": true, - "useSupported": true, - "useSwipe": true, - "useTemplateRefsList": true, - "useTextDirection": true, - "useTextSelection": true, - "useTextareaAutosize": true, - "useThrottle": true, - "useThrottleFn": true, - "useThrottledRefHistory": true, - "useTimeAgo": true, - "useTimeout": true, - "useTimeoutFn": true, - "useTimeoutPoll": true, - "useTimestamp": true, - "useTitle": true, - "useToNumber": true, - "useToString": true, - "useToggle": true, - "useTransition": true, - "useUrlSearchParams": true, - "useUserMedia": true, - "useVModel": true, - "useVModels": true, - "useVibrate": true, - "useVirtualList": true, - "useWakeLock": true, - "useWebNotification": true, - "useWebSocket": true, - "useWebWorker": true, - "useWebWorkerFn": true, - "useWindowFocus": true, - "useWindowScroll": true, - "useWindowSize": true, - "watch": true, - "watchArray": true, - "watchAtMost": true, - "watchDebounced": true, - "watchDeep": true, - "watchEffect": true, - "watchIgnorable": true, - "watchImmediate": true, - "watchOnce": true, - "watchPausable": true, - "watchPostEffect": true, - "watchSyncEffect": true, - "watchThrottled": true, - "watchTriggerable": true, - "watchWithFilter": true, - "useRoute": true, - "useRouter": true, - "storeToRefs": true, - "whenever": true, - "DirectiveBinding": true, - "ExtractDefaultPropTypes": true, - "ExtractPropTypes": true, - "ExtractPublicPropTypes": true, - "MaybeRef": true, - "MaybeRefOrGetter": true, - "WritableComputedRef": true, - "acceptHMRUpdate": true, - "createPinia": true, - "defineStore": true, - "getActivePinia": true, - "injectLocal": true, - "mapActions": true, - "mapGetters": true, - "mapState": true, - "mapStores": true, - "mapWritableState": true, - "onBeforeRouteLeave": true, - "onBeforeRouteUpdate": true, - "onWatcherCleanup": true, - "provideLocal": true, - "setActivePinia": true, - "setMapStoreSuffix": true, - "useClipboardItems": true, - "useI18n": true, - "useId": true, - "useLink": true, - "useModel": true, - "useTemplateRef": true - } -} diff --git a/.gitignore b/.gitignore index 494f1617..1b28f8ee 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ stats.html .env.local .env.*.local +# 线上部署专用变量,不入库 +.env.youlai + # Editor directories and files .idea/ .vscode/* @@ -34,4 +37,7 @@ package-lock.json yarn.lock # Local history -.history \ No newline at end of file +.history + +# Local deploy config +deploy.mjs \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit index e77b172c..26fc3643 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,2 +1,2 @@ #!/usr/bin/env sh -npm run lint:lint-staged +pnpm exec lint-staged diff --git a/.stylelintrc.cjs b/.stylelintrc.cjs index 0cbaf9b2..f608406b 100644 --- a/.stylelintrc.cjs +++ b/.stylelintrc.cjs @@ -7,6 +7,10 @@ module.exports = { "stylelint-config-recess-order", ], overrides: [ + { + files: ["**/*.css"], + rules: { "declaration-property-value-no-unknown": true }, + }, { files: ["**/*.{vue,html}"], customSyntax: "postcss-html", diff --git a/README.md b/README.md index b6533df8..991c010c 100644 --- a/README.md +++ b/README.md @@ -71,12 +71,12 @@ ## 快速开始 -**环境要求**:Node.js `^20.19.0` 或 `>=22.12.0` · pnpm `>=8.0.0` +**环境要求**:Node.js `^24.0.0` · pnpm `10.34.5` | 环境类型 | 版本要求 | 备注 | | -------- | -------- | ---- | -| **Node.js** | `^20.19.0` 或 `>=22.12.0` | 推荐 LTS 版本(主版本为偶数) | -| **包管理器** | `pnpm >= 8.0.0` | 项目使用 pnpm 作为包管理器 | +| **Node.js** | `^24.0.0` | 24 LTS;ESLint、lint-staged 等工具链要求 Node ≥ 22.22,统一到 24 | +| **包管理器** | `pnpm 10.34.5` | `packageManager` 字段已固定版本,请使用 Corepack 或同版本 pnpm | | **开发工具** | [Visual Studio Code](https://code.visualstudio.com/Download) | 推荐安装 Vue、TypeScript 相关插件 | ```bash @@ -85,7 +85,7 @@ git clone https://gitee.com/youlaiorg/vue3-element-admin.git cd vue3-element-admin # 安装 pnpm(已安装可跳过) -npm install pnpm -g +npm install --global pnpm@10.34.5 # 设置镜像源(可忽略) pnpm config set registry https://registry.npmmirror.com @@ -178,7 +178,7 @@ pnpm run build **本地 Mock**:将 `.env.development` 的 `VITE_MOCK_ENABLED` 设为 `true` 即启用本地 Mock 接口,无需后端即可独立开发。 -**对接后端**:九种后端默认端口均为 `8000`,将 `.env.development` 的 `VITE_PROXY_TARGET` 改为 `http://localhost:8000` 并启动对应后端即可(主推 Java 后端 [youlai-boot](https://gitee.com/youlaiorg/youlai-boot.git),其余见各自仓库 README)。 +**对接后端**:九种后端默认端口均为 `8000`,将 `.env.development` 的 `API_PROXY_TARGET` 改为 `http://localhost:8000` 并启动对应后端即可(主推 Java 后端 [youlai-boot](https://gitee.com/youlaiorg/youlai-boot.git),其余见各自仓库 README)。 ## 提交规范 diff --git a/deploy.example.mjs b/deploy.example.mjs deleted file mode 100644 index 9e898789..00000000 --- a/deploy.example.mjs +++ /dev/null @@ -1,58 +0,0 @@ -/* eslint-disable no-console */ -/** - * 部署模板:构建并上传到服务器 - * - * 用法:复制本文件为 deploy.mjs(已加入忽略,不入库),再创建 deploy.config.mjs - * 填写服务器信息,然后执行 pnpm run deploy - * - * 服务器信息取自 deploy.config.mjs,缺该文件时回落到环境变量 - * DEPLOY_HOST / DEPLOY_USER / DEPLOY_PORT / DEPLOY_TARGET - * - * 前置: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"; -import { existsSync } from "node:fs"; -import { resolve } from "node:path"; -import { pathToFileURL } from "node:url"; - -/** 读取部署配置,配置文件缺失时回落到环境变量 */ -async function readConfig() { - const configPath = resolve("deploy.config.mjs"); - if (existsSync(configPath)) { - return (await import(pathToFileURL(configPath).href)).default; - } - - return { - host: process.env.DEPLOY_HOST, - user: process.env.DEPLOY_USER, - port: Number(process.env.DEPLOY_PORT ?? 22), - target: process.env.DEPLOY_TARGET, - }; -} - -const config = await readConfig(); - -if (!config?.host || !config?.user || !config?.target) { - console.error("缺少部署配置:在项目根目录创建 deploy.config.mjs 填写 host / user / port / target"); - process.exit(1); -} - -const { host, user, port = 22, target } = config; - -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/deploy.mjs b/deploy.mjs deleted file mode 100644 index 255fc53e..00000000 --- a/deploy.mjs +++ /dev/null @@ -1,31 +0,0 @@ -/* eslint-disable no-console */ -/** - * 部署管理后台到服务器 | 用法: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 = "www.youlai.tech"; // 服务器 IP,改为你的 -const USER = "root"; // SSH 用户名,改为你的 -const PORT = 1010; // SSH 端口,改为你的 -const TARGET = "/mnt/nginx/html/vue"; // 部署目录,改为你的 -// ========================================================= - -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/eslint.config.ts b/eslint.config.ts index 96fb27d9..e71b979c 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -4,15 +4,6 @@ import * as typescriptEslint from "typescript-eslint"; import pluginVue from "eslint-plugin-vue"; import vueParser from "vue-eslint-parser"; import configPrettier from "eslint-config-prettier"; -import fs from "node:fs"; - -let autoImportGlobals: Record = {}; -try { - autoImportGlobals = - JSON.parse(fs.readFileSync("./.eslintrc-auto-import.json", "utf-8")).globals || {}; -} catch (error) { - console.warn("Could not load auto-import globals", error); -} export default [ { @@ -36,11 +27,7 @@ export default [ ecmaVersion: "latest", sourceType: "module", globals: { - ...globals.browser, - ...globals.node, ...globals.es2022, - ...autoImportGlobals, - __APP_INFO__: "readonly", }, }, plugins: { @@ -66,10 +53,23 @@ export default [ "no-unused-vars": "off", "no-undef": "off", "no-redeclare": "off", - "@typescript-eslint/ban-ts-comment": "off", }, }, + { + files: ["src/**/*.{js,ts,vue}"], + languageOptions: { globals: { ...globals.browser, __APP_INFO__: "readonly" } }, + }, + { + files: ["*.{js,mjs,cjs,ts}", "mock/**/*.ts"], + languageOptions: { globals: globals.node }, + rules: { "no-console": "off" }, + }, + { + files: ["**/*.{js,mjs,cjs}"], + rules: { "no-undef": "error" }, + }, + { files: ["**/*.vue"], languageOptions: { @@ -79,7 +79,7 @@ export default [ sourceType: "module", parser: typescriptEslint.parser, extraFileExtensions: [".vue"], - tsconfigRootDir: __dirname, + tsconfigRootDir: import.meta.dirname, }, }, rules: { @@ -88,10 +88,12 @@ export default [ "vue/require-default-prop": "off", "vue/require-explicit-emits": "error", "vue/no-unused-vars": "error", - "vue/no-mutating-props": "off", + "vue/no-mutating-props": "warn", "vue/valid-v-for": "warn", "vue/no-template-shadow": "warn", - "vue/return-in-computed-property": "warn", + // 组件由 unplugin-vue-components 自动注册,ESLint 侧拿不到已注册组件清单 + "vue/no-undef-components": "off", + "vue/return-in-computed-property": "error", "vue/block-order": [ "error", { @@ -111,7 +113,6 @@ export default [ }, ], "vue/component-name-in-template-casing": ["error", "PascalCase"], - "@typescript-eslint/no-explicit-any": "warn", }, }, @@ -123,15 +124,30 @@ export default [ ecmaVersion: "latest", sourceType: "module", project: "./tsconfig.eslint.json", - tsconfigRootDir: __dirname, + tsconfigRootDir: import.meta.dirname, }, }, rules: { - "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/no-empty-function": "off", "@typescript-eslint/no-empty-object-type": "off", - "@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/no-unused-expressions": "warn", + }, + }, + + { + // Vue 与 TS 共用的脚本规则:同一段代码放在 .ts 或 diff --git a/src/components/CommandPalette/useCommandPalette.ts b/src/components/CommandPalette/useCommandPalette.ts index 58f8c055..c3f44bde 100644 --- a/src/components/CommandPalette/useCommandPalette.ts +++ b/src/components/CommandPalette/useCommandPalette.ts @@ -4,7 +4,9 @@ import router from "@/router"; import { usePermissionStore } from "@/stores"; import { isExternal } from "@/utils"; -/** 命令面板中的可搜索菜单项 */ +/** + * 命令面板中的可搜索菜单项 + */ interface SearchItem { title: string; path: string; @@ -24,13 +26,11 @@ const EXCLUDED_PATHS = ["/redirect", "/login", "/401", "/404"]; export function useCommandPalette() { const permissionStore = usePermissionStore(); - // 面板状态 const visible = ref(false); const keyword = ref(""); const activeIndex = ref(-1); const inputRef = ref(); - // 菜单数据 const menuItems = ref([]); const results = ref([]); const history = ref([]); diff --git a/src/components/Crud/CrudForm.vue b/src/components/Crud/CrudForm.vue new file mode 100644 index 00000000..0f21af72 --- /dev/null +++ b/src/components/Crud/CrudForm.vue @@ -0,0 +1,473 @@ + + + diff --git a/src/components/Crud/CrudModal.vue b/src/components/Crud/CrudModal.vue deleted file mode 100644 index b8388100..00000000 --- a/src/components/Crud/CrudModal.vue +++ /dev/null @@ -1,290 +0,0 @@ - - - - - diff --git a/src/components/Crud/CrudPage.vue b/src/components/Crud/CrudPage.vue new file mode 100644 index 00000000..db0da445 --- /dev/null +++ b/src/components/Crud/CrudPage.vue @@ -0,0 +1,152 @@ + + + diff --git a/src/components/Crud/CrudSearch.vue b/src/components/Crud/CrudSearch.vue index e80609b5..a7aebd40 100644 --- a/src/components/Crud/CrudSearch.vue +++ b/src/components/Crud/CrudSearch.vue @@ -1,64 +1,70 @@