From 38d58394e32555a3502dd484b921a661548b4b63 Mon Sep 17 00:00:00 2001 From: vben Date: Fri, 24 May 2024 23:11:03 +0800 Subject: [PATCH] feat: add docker shell --- .dockerignore | 5 ++ .vscode/settings.json | 23 ++++- Dockerfile | 30 +++++++ apps/antd-view/src/bootstrap.ts | 33 +++++++ apps/antd-view/src/layouts/basic.vue | 16 +--- apps/antd-view/src/main.ts | 44 +++------- build-local-docker-image.sh | 55 ++++++++++++ deploy/nginx.conf | 87 +++++++++++++++++++ .../lint-configs/eslint-config/package.json | 2 +- internal/tailwind-config/package.json | 4 +- package.json | 3 +- packages/@vben-core/README.md | 3 + .../shared/typings/src/preference.ts | 2 + .../@vben-core/shared/typings/src/tools.ts | 29 +++++++ .../dropdown-menu/DropdownMenuSeparator.vue | 2 +- .../common-ui/src/preference/preference.vue | 12 ++- .../src/preference/use-open-preference.ts | 16 ++++ .../src/user-dropdown/user-dropdown.vue | 42 +++++---- .../business/layouts/src/basic/layout.vue | 2 +- packages/preference/src/cache.ts | 5 +- packages/preference/src/config.ts | 3 +- packages/preference/src/setup.ts | 14 +-- packages/stores/src/setup.ts | 13 +-- packages/styles/package.json | 7 +- pnpm-lock.yaml | 62 +++++++------ 25 files changed, 394 insertions(+), 120 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 apps/antd-view/src/bootstrap.ts create mode 100755 build-local-docker-image.sh create mode 100644 deploy/nginx.conf create mode 100644 packages/@vben-core/README.md create mode 100644 packages/business/common-ui/src/preference/use-open-preference.ts diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..4f75c951 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +node_modules +.git +.gitignore +*.md +dist diff --git a/.vscode/settings.json b/.vscode/settings.json index d5a75333..7c867e60 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,6 +12,8 @@ // editor "editor.tabSize": 2, + "editor.detectIndentation": false, + "editor.cursorBlinking": "expand", "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.fontFamily": "Input Mono, FiraCode-Retina, monospace", "editor.fontLigatures": true, @@ -20,9 +22,17 @@ "editor.cursorSmoothCaretAnimation": "on", "editor.guides.bracketPairs": "active", "editor.inlineSuggest.enabled": true, - "editor.suggestSelection": "first", + "editor.suggestSelection": "recentlyUsedByPrefix", + "editor.acceptSuggestionOnEnter": "smart", + "editor.suggest.snippetsPreventQuickSuggestions": false, "editor.stickyScroll.enabled": true, "editor.hover.sticky": true, + "editor.suggest.insertMode": "replace", + "editor.bracketPairColorization.enabled": true, + "editor.autoClosingBrackets": "beforeWhitespace", + "editor.autoClosingDelete": "always", + "editor.autoClosingOvertype": "always", + "editor.autoClosingQuotes": "beforeWhitespace", "editor.wordSeparators": "`~!@#%^&*()=+[{]}\\|;:'\",.<>/?", "editor.codeActionsOnSave": { "source.fixAll.eslint": "explicit", @@ -38,6 +48,7 @@ "terminal.integrated.persistentSessionReviveProcess": "never", "terminal.integrated.tabs.enabled": true, "terminal.integrated.scrollback": 10000, + "terminal.integrated.stickyScroll.enabled": true, // files "files.eol": "\n", @@ -46,8 +57,8 @@ "files.associations": { "*.ejs": "html", "*.art": "html", - "**/tsconfig.json": "jsonc" - // "*.json": "jsonc" + "**/tsconfig.json": "jsonc", + "*.json": "jsonc" }, "files.exclude": { @@ -77,6 +88,7 @@ }, // search + "search.searchEditor.singleClickBehaviour": "peekDefinition", "search.followSymlinks": false, // 在使用搜索功能时,将这些文件夹/文件排除在外 "search.exclude": { @@ -124,6 +136,10 @@ "stylelint.packageManager": "pnpm", "stylelint.validate": ["css", "less", "postcss", "scss", "vue", "sass"], + "typescript.inlayHints.enumMemberValues.enabled": true, + "typescript.preferences.preferTypeOnlyAutoImports": true, + "typescript.preferences.includePackageJsonAutoImports": "on", + // Enable the ESlint flat config support "eslint.experimental.useFlatConfig": true, "eslint.validate": [ @@ -179,6 +195,7 @@ "*.env": "$(capture).env.*", "README.md": "README*,CHANGELOG*,LICENSE,CNAME", "package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,.gitattributes,.gitignore,.gitpod.yml,.npmrc,.browserslistrc,.node-version,.git*,.tazerc.json", + "Dockerfile": "Dockerfile,.docker*,docker-entrypoint.sh,build-local-docker*", "eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,.ls-lint*", "tailwind.config.mjs": "postcss.*" }, diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..e46ff98e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,30 @@ +FROM node:20-slim AS builder + +# --max-old-space-size +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +ENV NODE_OPTIONS=--max-old-space-size=8192 +ENV TZ=Asia/Shanghai + +RUN corepack enable + +WORKDIR /app + +# copy package.json and pnpm-lock.yaml to workspace +COPY . /app + +RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile +RUN pnpm run build + +RUN echo "Builder Success 🎉" + +FROM nginx:stable-alpine as production + +COPY --from=builder /app/apps/antd-view/dist /usr/share/nginx/html + +COPY ./deploy/nginx.conf /etc/nginx/nginx.conf + +EXPOSE 8080 + +# start nginx +CMD ["nginx", "-g", "daemon off;"] diff --git a/apps/antd-view/src/bootstrap.ts b/apps/antd-view/src/bootstrap.ts new file mode 100644 index 00000000..88cdfe2b --- /dev/null +++ b/apps/antd-view/src/bootstrap.ts @@ -0,0 +1,33 @@ +import '@vben/styles'; + +import { setupI18n } from '@vben/locales'; +import { preference } from '@vben/preference'; +import { setupStore } from '@vben/stores'; +import { createApp } from 'vue'; + +import App from './app.vue'; +import { router } from './router'; + +async function bootstrap(namespace: string, env: string) { + const app = createApp(App); + + // 国际化 i18n 配置 + await setupI18n(app, { defaultLocale: preference.locale }); + + // 配置 pinia-store + await setupStore(app, { env, namespace }); + + // 配置路由及路由守卫 + app.use(router); + + app.mount('#app'); + + // production mock server + if (import.meta.env.PROD) { + import('./mock-prod-server').then(({ setupProdMockServer }) => { + setupProdMockServer(); + }); + } +} + +export { bootstrap }; diff --git a/apps/antd-view/src/layouts/basic.vue b/apps/antd-view/src/layouts/basic.vue index c3482ea5..996113ac 100644 --- a/apps/antd-view/src/layouts/basic.vue +++ b/apps/antd-view/src/layouts/basic.vue @@ -2,12 +2,7 @@ import type { NotificationItem } from '@vben/common-ui'; import { Notification, UserDropdown } from '@vben/common-ui'; -import { - IcRoundCreditScore, - IcRoundSettingsSuggest, - MdiDriveDocument, - MdiGithub, -} from '@vben/icons'; +import { IcRoundCreditScore, MdiDriveDocument, MdiGithub } from '@vben/icons'; import { BasicLayout } from '@vben/layouts'; import { $t } from '@vben/locales'; import { preference } from '@vben/preference'; @@ -79,15 +74,6 @@ const menus = computed(() => [ icon: IcRoundCreditScore, text: $t('widgets.qa'), }, - { - handler: () => { - // openWindow('https://github.com/vbenjs/vue-vben-admin', { - // target: '_blank', - // }); - }, - icon: IcRoundSettingsSuggest, - text: $t('widgets.setting'), - }, ]); const accessStore = useAccessStore(); diff --git a/apps/antd-view/src/main.ts b/apps/antd-view/src/main.ts index 85ca06f3..d0960dff 100644 --- a/apps/antd-view/src/main.ts +++ b/apps/antd-view/src/main.ts @@ -1,40 +1,22 @@ -import '@vben/styles'; +import { setupPreference } from '@vben/preference'; -import { setupI18n } from '@vben/locales'; -import { preference, setupPreference } from '@vben/preference'; -import { setupStore } from '@vben/stores'; -import { createApp } from 'vue'; - -import App from './app.vue'; import { overridesPreference } from './preference'; -import { router } from './router'; -async function bootstrap(cachePrefix: string) { - // app偏好设置 +/** + * 应用初始化完成之后再进行页面加载渲染 + */ +async function initApplication() { + const namespace = 'antd-view'; + const env = import.meta.env.PROD ? 'prod' : 'dev'; + + // app偏好设置初始化 await setupPreference({ - cachePrefix, + env, + namespace, overrides: overridesPreference, }); - const app = createApp(App); - - // 国际化 i18n 配置 - await setupI18n(app, { defaultLocale: preference.locale }); - - // 配置 pinia-store - await setupStore(app, { cachePrefix }); - - // 配置路由及路由守卫 - app.use(router); - - app.mount('#app'); - - // production mock server - if (import.meta.env.PROD) { - import('./mock-prod-server').then(({ setupProdMockServer }) => { - setupProdMockServer(); - }); - } + import('./bootstrap').then((m) => m.bootstrap(namespace, env)); } -bootstrap('vben-admin-pro-antd'); +initApplication(); diff --git a/build-local-docker-image.sh b/build-local-docker-image.sh new file mode 100755 index 00000000..8db617a1 --- /dev/null +++ b/build-local-docker-image.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +LOG_FILE=${SCRIPT_DIR}/build-local-docker-image.log +ERROR="" +IMAGE_NAME="vben-admin-pro-local" + +function stop_and_remove_container() { + # Stop and remove the existing container + docker stop ${IMAGE_NAME} >/dev/null 2>&1 + docker rm ${IMAGE_NAME} >/dev/null 2>&1 +} + +function remove_image() { + # Remove the existing image + docker rmi vben-admin-pro >/dev/null 2>&1 +} + +function install_dependencies() { + # Install all dependencies + cd ${SCRIPT_DIR} + pnpm install || ERROR="install_dependencies failed" +} + +function build_image() { + # build docker + docker build . -f Dockerfile -t ${IMAGE_NAME} || ERROR="build_image failed" +} + +function log_message() { + if [[ ${ERROR} != "" ]]; + then + >&2 echo "build failed, Please check build-local-docker-image.log for more details" + >&2 echo "ERROR: ${ERROR}" + exit 1 + else + echo "docker image with tag '${IMAGE_NAME}' built sussessfully. Use below sample command to run the container" + echo "" + echo "docker run -d -p 8010:8080 --name ${IMAGE_NAME} ${IMAGE_NAME}" + fi +} + +echo "Info: Stopping and removing existing container and image" | tee ${LOG_FILE} +stop_and_remove_container +remove_image + +echo "Info: Installing dependencies" | tee -a ${LOG_FILE} +install_dependencies 1>> ${LOG_FILE} 2>> ${LOG_FILE} + +if [[ ${ERROR} == "" ]]; then + echo "Info: Building docker image" | tee -a ${LOG_FILE} + build_image 1>> ${LOG_FILE} 2>> ${LOG_FILE} +fi + +log_message | tee -a ${LOG_FILE} diff --git a/deploy/nginx.conf b/deploy/nginx.conf new file mode 100644 index 00000000..a36247d0 --- /dev/null +++ b/deploy/nginx.conf @@ -0,0 +1,87 @@ + +#user nobody; +worker_processes 1; + +#error_log logs/error.log; +#error_log logs/error.log notice; +#error_log logs/error.log info; + +#pid logs/nginx.pid; + + +events { + worker_connections 1024; +} + + +http { + include mime.types; + default_type application/octet-stream; + + types { + application/javascript js mjs; + text/css css; + text/html html; + } + + sendfile on; + # tcp_nopush on; + + #keepalive_timeout 0; + # keepalive_timeout 65; + + # gzip on; + # gzip_buffers 32 16k; + # gzip_comp_level 6; + # gzip_min_length 1k; + # gzip_static on; + # gzip_types text/plain + # text/css + # application/javascript + # application/json + # application/x-javascript + # text/xml + # application/xml + # application/xml+rss + # text/javascript; #设置压缩的文件类型 + # gzip_vary on; + + server { + listen 8080; + server_name localhost; + + location / { + root /usr/share/nginx/html; + try_files $uri $uri/ /index.html; + index index.html; + # Enable CORS + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain charset=UTF-8'; + add_header 'Content-Length' 0; + return 204; + } + } + + error_page 500 502 503 504 /50x.html; + + location = /50x.html { + root /usr/share/nginx/html; + } + } +} + +# stream { # stream 模块配置和 http 模块在相同级别 +# upstream redis { +# server 127.0.0.1:6379 max_fails=3 fail_timeout=30s; +# } +# server { +# listen 16379; +# proxy_connect_timeout 1s; +# proxy_timeout 3s; +# proxy_pass redis; +# } +# } diff --git a/internal/lint-configs/eslint-config/package.json b/internal/lint-configs/eslint-config/package.json index aa3df140..61a2bf4e 100644 --- a/internal/lint-configs/eslint-config/package.json +++ b/internal/lint-configs/eslint-config/package.json @@ -43,7 +43,7 @@ "eslint-config-prettier": "^9.1.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-i": "^2.29.1", - "eslint-plugin-jsdoc": "^48.2.5", + "eslint-plugin-jsdoc": "^48.2.6", "eslint-plugin-jsonc": "^2.15.1", "eslint-plugin-n": "^17.7.0", "eslint-plugin-no-only-tests": "^3.1.0", diff --git a/internal/tailwind-config/package.json b/internal/tailwind-config/package.json index e417d53c..5ee39454 100644 --- a/internal/tailwind-config/package.json +++ b/internal/tailwind-config/package.json @@ -47,14 +47,14 @@ "./*": "./*" }, "dependencies": { - "@iconify/json": "^2.2.212", + "@iconify/json": "^2.2.213", "@iconify/tailwind": "^1.1.1", "@tailwindcss/forms": "^0.5.7", "autoprefixer": "^10.4.19", "cssnano": "^7.0.1", "postcss": "^8.4.38", "postcss-antd-fixes": "^0.2.0", - "postcss-preset-env": "^9.5.13", + "postcss-preset-env": "^9.5.14", "tailwindcss": "^3.4.3", "tailwindcss-animate": "^1.0.7" }, diff --git a/package.json b/package.json index e9aa44bf..95e21fcc 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,8 @@ "format": "vsh lint --format", "prepare": "is-ci || husky", "reinstall": "pnpm clean --del-lock && pnpm bootstrap", - "test": "vitest" + "test": "vitest", + "build:docker": "./build-local-docker-image.sh" }, "devDependencies": { "@changesets/cli": "^2.27.3", diff --git a/packages/@vben-core/README.md b/packages/@vben-core/README.md new file mode 100644 index 00000000..0ee2c7b4 --- /dev/null +++ b/packages/@vben-core/README.md @@ -0,0 +1,3 @@ +# @vben-core + +系统一些比较基础的SDK和UI组件库,请勿将任何业务逻辑和业务包放在这里。 diff --git a/packages/@vben-core/shared/typings/src/preference.ts b/packages/@vben-core/shared/typings/src/preference.ts index 4e68c2f4..777c31cd 100644 --- a/packages/@vben-core/shared/typings/src/preference.ts +++ b/packages/@vben-core/shared/typings/src/preference.ts @@ -91,6 +91,8 @@ interface Preference { pageTransitionEnable: boolean; /** 是否开启半深色菜单(只在theme='light'时生效) */ semiDarkMenu: boolean; + /** 是否显示偏好设置 */ + showPreference: boolean; /** 侧边栏是否折叠 */ sideCollapse: boolean; /** 侧边栏折叠时,是否显示title */ diff --git a/packages/@vben-core/shared/typings/src/tools.ts b/packages/@vben-core/shared/typings/src/tools.ts index faae162d..679e8d07 100644 --- a/packages/@vben-core/shared/typings/src/tools.ts +++ b/packages/@vben-core/shared/typings/src/tools.ts @@ -80,6 +80,33 @@ type MaybeReadonlyRef = (() => T) | ComputedRef; */ type MaybeComputedRef = MaybeReadonlyRef | MaybeRef; +type Merge = { + [K in keyof O | keyof T]: K extends keyof T + ? T[K] + : K extends keyof O + ? O[K] + : never; +}; + +/** + * T = [ + * { name: string; age: number; }, + * { sex: 'male' | 'female'; age: string } + * ] + * => + * MergeAll = { + * name: string; + * sex: 'male' | 'female'; + * age: string + * } + */ +type MergeAll< + T extends object[], + R extends object = Record, +> = T extends [infer F extends object, ...infer Rest extends object[]] + ? MergeAll> + : R; + export { type AnyFunction, type AnyNormalFunction, @@ -89,6 +116,8 @@ export { type IntervalHandle, type MaybeComputedRef, type MaybeReadonlyRef, + type Merge, + type MergeAll, type NonNullable, type Nullable, type ReadonlyRecordable, diff --git a/packages/@vben-core/uikit/shadcn-ui/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue b/packages/@vben-core/uikit/shadcn-ui/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue index 2cbff17f..e189bf8a 100644 --- a/packages/@vben-core/uikit/shadcn-ui/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue +++ b/packages/@vben-core/uikit/shadcn-ui/src/components/ui/dropdown-menu/DropdownMenuSeparator.vue @@ -22,6 +22,6 @@ const delegatedProps = computed(() => { diff --git a/packages/business/common-ui/src/preference/preference.vue b/packages/business/common-ui/src/preference/preference.vue index ab0a5ea0..91d3d6b0 100644 --- a/packages/business/common-ui/src/preference/preference.vue +++ b/packages/business/common-ui/src/preference/preference.vue @@ -14,7 +14,7 @@ import { import { $t } from '@vben/locales'; import { preference, resetPreference, usePreference } from '@vben/preference'; import { useClipboard } from '@vueuse/core'; -import { computed, ref } from 'vue'; +import { computed } from 'vue'; import { Animation, @@ -33,6 +33,7 @@ import { ThemeColor, } from './blocks'; import Trigger from './trigger.vue'; +import { useOpenPreference } from './use-open-preference'; withDefaults(defineProps<{ colorPrimaryPresets: string[] }>(), { colorPrimaryPresets: () => [], @@ -106,11 +107,7 @@ const showBreadcrumbConfig = computed(() => { ); }); -const openSheet = ref(false); - -function handlerOpenSheet() { - openSheet.value = true; -} +const { openPreference } = useOpenPreference(); async function handleCopy() { await copy(JSON.stringify(diffPreference.value, null, 2)); @@ -130,11 +127,12 @@ function handleReset() {