diff --git a/.changeset/.prettierrc.json b/.changeset/.prettierrc.json new file mode 100644 index 00000000..544138be --- /dev/null +++ b/.changeset/.prettierrc.json @@ -0,0 +1,3 @@ +{ + "singleQuote": true +} diff --git a/.changeset/fancy-ears-walk.md b/.changeset/fancy-ears-walk.md new file mode 100644 index 00000000..f73cb596 --- /dev/null +++ b/.changeset/fancy-ears-walk.md @@ -0,0 +1,7 @@ +--- +'@vben/styles': patch +'@vben-core/form-ui': patch +'@vben/web-naive': patch +--- + +feat(@core/form-ui): 新增 useVbenForm 数组编辑器 VbenFormFieldArray diff --git a/.gitattributes b/.gitattributes index d4e5bd3e..775caa34 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,8 +4,18 @@ * text=auto eol=lf # Declare files that will always have CRLF line endings on checkout. -*.{cmd,[cC][mM][dD]} text eol=crlf -*.{bat,[bB][aA][tT]} text eol=crlf +*.cmd text eol=crlf +*.[cC][mM][dD] text eol=crlf +*.bat text eol=crlf +*.[bB][aA][tT] text eol=crlf # Denote all files that are truly binary and should not be modified. -*.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary \ No newline at end of file +*.ico binary +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.webp binary +*.svg binary +*.woff binary +*.woff2 binary \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9fd86547..b95ff946 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,14 +1,14 @@ # default onwer -* anncwb@126.com vince292007@gmail.com netfan@foxmail.com +* anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com # vben core onwer -/.github/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com -/.vscode/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com -/packages/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com -/packages/@core/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com -/internal/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com -/scripts/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com +/.github/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com +/.vscode/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com +/packages/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com +/packages/@core/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com +/internal/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com +/scripts/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com jinmao88@qq.com # vben team onwer -apps/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5 -docs/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5 +apps/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5 jinmao88@qq.com +docs/ anncwb@126.com vince292007@gmail.com netfan@foxmail.com @vbenjs/team-v5 jinmao88@qq.com diff --git a/.github/actions/setup-node/action.yml b/.github/actions/setup-node/action.yml index 35fa41c8..445f30be 100644 --- a/.github/actions/setup-node/action.yml +++ b/.github/actions/setup-node/action.yml @@ -6,10 +6,10 @@ runs: using: 'composite' steps: - name: Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 - name: Install Node.js - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version-file: .node-version cache: 'pnpm' diff --git a/.github/config.yml b/.github/config.yml index 52454840..248da6a5 100644 --- a/.github/config.yml +++ b/.github/config.yml @@ -21,12 +21,10 @@ newPRWelcomeComment: | firstPRMergeComment: > Thanks for your contribution! 🎉🎉🎉 - # Comment to be posted to on first time issues newIssueWelcomeComment: > Thanks for opening your first issue! Be sure to follow the issue template and provide every bit of information to help the developers! - # *OPTIONAL* default titles to check against for lack of descriptiveness # MUST BE ALL LOWERCASE requestInfoDefaultTitles: @@ -36,4 +34,3 @@ requestInfoDefaultTitles: # *Required* Comment to reply with requestInfoReplyComment: > Thanks for filing this issue/PR! It would be much appreciated if you could provide us with more information so we can effectively analyze the situation in context. - diff --git a/.github/contributing.md b/.github/contributing.md index f22370f3..304c5192 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -19,11 +19,9 @@ Project maintainers have the right and responsibility to remove, edit, or reject - Checkout a topic branch from the relevant branch, e.g. main, and merge back against that branch. - If adding a new feature: - - Provide a convincing reason to add this feature. Ideally, you should open a suggestion issue first and have it approved before working on it. - If fixing bug: - - Provide a detailed description of the bug in the PR. Live demo preferred. - It's OK to have multiple small commits as you work on the PR - GitHub can automatically squash them before merging. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d9f48e4a..b81bc4ad 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,7 +30,7 @@ jobs: - windows-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 0 diff --git a/.github/workflows/changeset-version.yml b/.github/workflows/changeset-version.yml index 373d9af2..32421e26 100644 --- a/.github/workflows/changeset-version.yml +++ b/.github/workflows/changeset-version.yml @@ -25,7 +25,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 0 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 00750949..254ffd8e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,12 +28,12 @@ jobs: timeout-minutes: 20 steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 0 - name: Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 with: run_install: false @@ -67,7 +67,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 0 @@ -90,7 +90,7 @@ jobs: - windows-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 0 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 69a990f0..4824318e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -57,11 +57,11 @@ jobs: # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v7 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -89,6 +89,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 with: category: '/language:${{matrix.language}}' diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 533a87c3..8c12d408 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 0 @@ -30,7 +30,7 @@ jobs: run: pnpm build:play - name: Sync Playground files - uses: SamKirkland/FTP-Deploy-Action@v4.3.5 + uses: SamKirkland/FTP-Deploy-Action@v4.4.0 with: server: ${{ secrets.PRO_FTP_HOST }} username: ${{ secrets.WEB_PLAYGROUND_FTP_ACCOUNT }} @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 0 @@ -54,7 +54,7 @@ jobs: run: pnpm build:docs - name: Sync Docs files - uses: SamKirkland/FTP-Deploy-Action@v4.3.5 + uses: SamKirkland/FTP-Deploy-Action@v4.4.0 with: server: ${{ secrets.PRO_FTP_HOST }} username: ${{ secrets.WEBSITE_FTP_ACCOUNT }} @@ -67,7 +67,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 0 @@ -85,7 +85,7 @@ jobs: run: pnpm run build:antd - name: Sync files - uses: SamKirkland/FTP-Deploy-Action@v4.3.5 + uses: SamKirkland/FTP-Deploy-Action@v4.4.0 with: server: ${{ secrets.PRO_FTP_HOST }} username: ${{ secrets.WEB_ANTD_FTP_ACCOUNT }} @@ -98,7 +98,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 0 @@ -116,7 +116,7 @@ jobs: run: pnpm run build:ele - name: Sync files - uses: SamKirkland/FTP-Deploy-Action@v4.3.5 + uses: SamKirkland/FTP-Deploy-Action@v4.4.0 with: server: ${{ secrets.PRO_FTP_HOST }} username: ${{ secrets.WEB_ELE_FTP_ACCOUNT }} @@ -129,7 +129,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 0 @@ -147,9 +147,26 @@ jobs: run: pnpm run build:naive - name: Sync files - uses: SamKirkland/FTP-Deploy-Action@v4.3.5 + uses: SamKirkland/FTP-Deploy-Action@v4.4.0 with: server: ${{ secrets.PRO_FTP_HOST }} username: ${{ secrets.WEB_NAIVE_FTP_ACCOUNT }} password: ${{ secrets.WEB_NAIVE_FTP_PASSWORD }} local-dir: ./apps/web-naive/dist/ + + rerun-on-failure: + name: Rerun on failure + needs: + - deploy-playground-ftp + - deploy-docs-ftp + - deploy-antd-ftp + - deploy-ele-ftp + - deploy-naive-ftp + if: failure() && fromJSON(github.run_attempt) < 10 + runs-on: ubuntu-latest + steps: + - name: Retry ${{ fromJSON(github.run_attempt) }} of 10 + env: + GH_REPO: ${{ github.repository }} + GH_TOKEN: ${{ github.token }} + run: gh workflow run rerun.yml -F run_id=${{ github.run_id }} diff --git a/.github/workflows/draft.yml b/.github/workflows/draft.yml index 20b4148a..babca2d3 100644 --- a/.github/workflows/draft.yml +++ b/.github/workflows/draft.yml @@ -20,6 +20,6 @@ jobs: if: github.repository == 'vbenjs/vue-vben-admin' runs-on: ubuntu-latest steps: - - uses: release-drafter/release-drafter@v6 + - uses: release-drafter/release-drafter@v7 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/issue-close-require.yml b/.github/workflows/issue-close-require.yml index 18c007bf..794292f4 100644 --- a/.github/workflows/issue-close-require.yml +++ b/.github/workflows/issue-close-require.yml @@ -19,7 +19,7 @@ jobs: steps: # 关闭未活动的 Issues - name: Close Inactive Issues - uses: actions/stale@v9 + uses: actions/stale@v11 with: days-before-stale: -1 # Issues and PR will never be flagged stale automatically. stale-issue-label: needs-reproduction # Label that flags an issue as stale. diff --git a/.github/workflows/issue-labeled.yml b/.github/workflows/issue-labeled.yml index 2feda4ef..7a461e3b 100644 --- a/.github/workflows/issue-labeled.yml +++ b/.github/workflows/issue-labeled.yml @@ -18,7 +18,7 @@ jobs: steps: - name: remove enhancement pending if: github.event.label.name == 'enhancement' - uses: actions-cool/issues-helper@v3 + uses: actions-cool/issues-helper-backup@d65454423c6fbbd20026b9b499d403f79422ac69 with: actions: 'remove-labels' token: ${{ secrets.GITHUB_TOKEN }} @@ -27,7 +27,7 @@ jobs: - name: remove bug pending if: github.event.label.name == 'bug' - uses: actions-cool/issues-helper@v3 + uses: actions-cool/issues-helper-backup@d65454423c6fbbd20026b9b499d403f79422ac69 with: actions: 'remove-labels' token: ${{ secrets.GITHUB_TOKEN }} @@ -36,7 +36,7 @@ jobs: - name: needs reproduction if: github.event.label.name == 'needs reproduction' - uses: actions-cool/issues-helper@v3 + uses: actions-cool/issues-helper-backup@d65454423c6fbbd20026b9b499d403f79422ac69 with: actions: 'create-comment, remove-labels' token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index bd73d533..a12227ef 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -14,7 +14,7 @@ jobs: if: github.repository == 'vbenjs/vue-vben-admin' runs-on: ubuntu-latest steps: - - uses: dessant/lock-threads@v5 + - uses: dessant/lock-threads@v6 with: github-token: ${{ secrets.GITHUB_TOKEN }} issue-inactive-days: '14' diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 7f524f90..d87a7011 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -3,78 +3,48 @@ name: Create Release Tag on: push: tags: - - 'v*.*.*' # Push events to matching v*, i.e. v1.0, v20.15.10 - -env: - HUSKY: '0' + - 'v*.*.*' + workflow_dispatch: + inputs: + tag: + description: 'Tag to create (e.g. v1.2.3)' + required: true + type: string permissions: - pull-requests: write contents: write jobs: - build: + release: name: Create Release if: github.repository == 'vbenjs/vue-vben-admin' runs-on: ubuntu-latest - strategy: - matrix: - node-version: [20] steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 0 - # - name: Checkout code - # uses: actions/checkout@v4 - # with: - # fetch-depth: 0 - - # - name: Install pnpm - # uses: pnpm/action-setup@v4 - - # - name: Use Node.js ${{ matrix.node-version }} - # uses: actions/setup-node@v4 - # with: - # node-version: ${{ matrix.node-version }} - # cache: "pnpm" - - # - name: Install dependencies - # run: pnpm install --frozen-lockfile - - # - name: Test and Build - # run: | - # pnpm run test - # pnpm run build - - - name: version + - name: Extract version id: version run: | - tag=${GITHUB_REF/refs\/tags\//} - version=${tag#v} - major=${version%%.*} - echo "tag=${tag}" >> $GITHUB_OUTPUT - echo "version=${version}" >> $GITHUB_OUTPUT - echo "major=${major}" >> $GITHUB_OUTPUT + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + raw_tag="${{ inputs.tag }}" + else + raw_tag="${GITHUB_REF_NAME}" + fi + # Normalize: ensure v prefix + tag="${raw_tag}" + [[ "${tag:0:1}" != "v" ]] && tag="v${tag}" + version="${tag#v}" + major="${version%%.*}" + echo "tag=${tag}" >> "${GITHUB_OUTPUT}" + echo "version=${version}" >> "${GITHUB_OUTPUT}" + echo "major=${major}" >> "${GITHUB_OUTPUT}" - - uses: release-drafter/release-drafter@v6 + - uses: release-drafter/release-drafter@v7 with: version: ${{ steps.version.outputs.version }} publish: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - # - name: force update major tag - # run: | - # git tag v${{ steps.version.outputs.major }} ${{ steps.version.outputs.tag }} -f - # git push origin refs/tags/v${{ steps.version.outputs.major }} -f - - # - name: Create Release for Tag - # id: release_tag - # uses: ncipollo/release-action@v1 - # with: - # token: ${{ secrets.GITHUB_TOKEN }} - # generateReleaseNotes: "true" - # body: | - # > Please refer to [CHANGELOG.md](https://github.com/vbenjs/vue-vben-admin/blob/main/CHANGELOG.md) for details. diff --git a/.github/workflows/rerun.yml b/.github/workflows/rerun.yml new file mode 100644 index 00000000..2b462551 --- /dev/null +++ b/.github/workflows/rerun.yml @@ -0,0 +1,19 @@ +name: Rerun workflow + +on: + workflow_dispatch: + inputs: + run_id: + description: The workflow id to relanch + required: true +jobs: + rerun: + runs-on: ubuntu-latest + steps: + - name: rerun ${{ inputs.run_id }} + env: + GH_REPO: ${{ github.repository }} + GH_TOKEN: ${{ github.token }} + run: | + gh run watch ${{ inputs.run_id }} > /dev/null 2>&1 + gh run rerun ${{ inputs.run_id }} --failed diff --git a/.github/workflows/semantic-pull-request.yml b/.github/workflows/semantic-pull-request.yml index db8e9e09..02db08de 100644 --- a/.github/workflows/semantic-pull-request.yml +++ b/.github/workflows/semantic-pull-request.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Validate PR title - uses: amannn/action-semantic-pull-request@v5 + uses: amannn/action-semantic-pull-request@v6 with: wip: true subjectPattern: ^(?![A-Z]).+$ diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 991e1331..a6fd1c0e 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -9,7 +9,7 @@ jobs: if: github.repository == 'vbenjs/vue-vben-admin' runs-on: ubuntu-latest steps: - - uses: actions/stale@v9 + - uses: actions/stale@v11 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days' diff --git a/.gitignore b/.gitignore index f73c2ad7..14538a4d 100644 --- a/.gitignore +++ b/.gitignore @@ -22,7 +22,8 @@ yarn.lock package-lock.json .VSCodeCounter **/backend-mock/data - +.omx +.pnpm-store # local env files .env.local .env.*.local @@ -51,3 +52,14 @@ vite.config.ts.* .history /.mimocode/ /.cursor/skills/ +.cursor + +# AI +.agent +.agents +.claude +.codex +skills-lock.json +.atomcode +datalog +.playwright-mcp diff --git a/.gitpod.yml b/.gitpod.yml index fb75b433..5fda2cf7 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -2,5 +2,5 @@ ports: - port: 5555 onOpen: open-preview tasks: - - init: corepack enable && pnpm install + - init: npm i -g corepack && pnpm install command: pnpm run dev:play diff --git a/.husky/commit-msg b/.husky/commit-msg deleted file mode 100755 index 270ebb8c..00000000 --- a/.husky/commit-msg +++ /dev/null @@ -1,6 +0,0 @@ -echo Start running commit-msg hook... - -# Check whether the git commit information is standardized -pnpm exec commitlint --edit "$1" - -echo Run commit-msg hook done. diff --git a/.husky/post-merge b/.husky/post-merge deleted file mode 100644 index 83fa775d..00000000 --- a/.husky/post-merge +++ /dev/null @@ -1,3 +0,0 @@ -# 每次 git pull 之后, 安装依赖 - -pnpm install diff --git a/.lintstagedrc.mjs b/.lintstagedrc.mjs deleted file mode 100644 index e68d8a3e..00000000 --- a/.lintstagedrc.mjs +++ /dev/null @@ -1,20 +0,0 @@ -export default { - '*.{js,jsx,ts,tsx}': [ - 'prettier --cache --ignore-unknown --write', - 'eslint --cache --fix', - ], - '*.{scss,less,styl,html,vue,css}': [ - 'prettier --cache --ignore-unknown --write', - 'stylelint --fix --allow-empty-input', - ], - '*.md': ['prettier --cache --ignore-unknown --write'], - '*.vue': [ - 'prettier --write', - 'eslint --cache --fix', - 'stylelint --fix --allow-empty-input', - ], - '{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': [ - 'prettier --cache --write--parser json', - ], - 'package.json': ['prettier --cache --write'], -}; diff --git a/.node-version b/.node-version index 48b14e6b..b832e400 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -20.14.0 +24.16.0 diff --git a/.npmrc b/.npmrc index f4a1ad48..7549542d 100644 --- a/.npmrc +++ b/.npmrc @@ -1,13 +1 @@ -registry = "https://registry.npmmirror.com" -public-hoist-pattern[]=husky -public-hoist-pattern[]=eslint -public-hoist-pattern[]=prettier -public-hoist-pattern[]=prettier-plugin-tailwindcss -public-hoist-pattern[]=stylelint -public-hoist-pattern[]=*postcss* -public-hoist-pattern[]=@commitlint/* -public-hoist-pattern[]=czg - -strict-peer-dependencies=false -auto-install-peers=true -dedupe-peer-dependents=true +registry=https://registry.npmmirror.com diff --git a/.prettierignore b/.prettierignore deleted file mode 100644 index d0b0ca13..00000000 --- a/.prettierignore +++ /dev/null @@ -1,18 +0,0 @@ -dist -dev-dist -.local -.output.js -node_modules -.nvmrc -coverage -CODEOWNERS -.nitro -.output - - -**/*.svg -**/*.sh - -public -.npmrc -*-lock.yaml diff --git a/.prettierrc.mjs b/.prettierrc.mjs deleted file mode 100644 index 3e25d2cf..00000000 --- a/.prettierrc.mjs +++ /dev/null @@ -1 +0,0 @@ -export { default } from '@vben/prettier-config'; diff --git a/.stylelintignore b/.stylelintignore index f4b2db2c..3adb33b2 100644 --- a/.stylelintignore +++ b/.stylelintignore @@ -2,3 +2,7 @@ dist public __tests__ coverage +.codex +.claude +.agent +.agents diff --git a/apps/backend-mock/api/demo/bigint.ts b/apps/backend-mock/api/demo/bigint.ts new file mode 100644 index 00000000..00d6c28c --- /dev/null +++ b/apps/backend-mock/api/demo/bigint.ts @@ -0,0 +1,32 @@ +import { eventHandler, setHeader } from 'h3'; +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { unAuthorizedResponse } from '~/utils/response'; + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + const data = ` + { + "code": 0, + "message": "success", + "data": [ + { + "id": 123456789012345678901234567890123456789012345678901234567890, + "name": "John Doe", + "age": 30, + "email": "john-doe@demo.com" + }, + { + "id": 987654321098765432109876543210987654321098765432109876543210, + "name": "Jane Smith", + "age": 25, + "email": "jane@demo.com" + } + ] + } + `; + setHeader(event, 'Content-Type', 'application/json'); + return data; +}); diff --git a/apps/backend-mock/api/system/dept/.post.ts b/apps/backend-mock/api/system/dept/.post.ts new file mode 100644 index 00000000..9a4896af --- /dev/null +++ b/apps/backend-mock/api/system/dept/.post.ts @@ -0,0 +1,16 @@ +import { eventHandler } from 'h3'; +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { + sleep, + unAuthorizedResponse, + useResponseSuccess, +} from '~/utils/response'; + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + await sleep(600); + return useResponseSuccess(null); +}); diff --git a/apps/backend-mock/api/system/dept/[id].delete.ts b/apps/backend-mock/api/system/dept/[id].delete.ts new file mode 100644 index 00000000..eac0f584 --- /dev/null +++ b/apps/backend-mock/api/system/dept/[id].delete.ts @@ -0,0 +1,16 @@ +import { eventHandler } from 'h3'; +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { + sleep, + unAuthorizedResponse, + useResponseSuccess, +} from '~/utils/response'; + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + await sleep(1000); + return useResponseSuccess(null); +}); diff --git a/apps/backend-mock/api/system/dept/[id].put.ts b/apps/backend-mock/api/system/dept/[id].put.ts new file mode 100644 index 00000000..6805e139 --- /dev/null +++ b/apps/backend-mock/api/system/dept/[id].put.ts @@ -0,0 +1,16 @@ +import { eventHandler } from 'h3'; +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { + sleep, + unAuthorizedResponse, + useResponseSuccess, +} from '~/utils/response'; + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + await sleep(2000); + return useResponseSuccess(null); +}); diff --git a/apps/backend-mock/api/system/dept/list.ts b/apps/backend-mock/api/system/dept/list.ts new file mode 100644 index 00000000..a649a0d2 --- /dev/null +++ b/apps/backend-mock/api/system/dept/list.ts @@ -0,0 +1,62 @@ +import { faker } from '@faker-js/faker'; +import { eventHandler } from 'h3'; +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; + +const formatterCN = new Intl.DateTimeFormat('zh-CN', { + timeZone: 'Asia/Shanghai', + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', +}); + +function generateMockDataList(count: number) { + const dataList = []; + + for (let i = 0; i < count; i++) { + const dataItem: Record = { + id: faker.string.uuid(), + pid: 0, + name: faker.commerce.department(), + status: faker.helpers.arrayElement([0, 1]), + createTime: formatterCN.format( + faker.date.between({ from: '2021-01-01', to: '2022-12-31' }), + ), + remark: faker.lorem.sentence(), + }; + if (faker.datatype.boolean()) { + dataItem.children = Array.from( + { length: faker.number.int({ min: 1, max: 5 }) }, + () => ({ + id: faker.string.uuid(), + pid: dataItem.id, + name: faker.commerce.department(), + status: faker.helpers.arrayElement([0, 1]), + createTime: formatterCN.format( + faker.date.between({ from: '2023-01-01', to: '2023-12-31' }), + ), + remark: faker.lorem.sentence(), + }), + ); + } + dataList.push(dataItem); + } + + return dataList; +} + +const mockData = generateMockDataList(10); + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + + const listData = structuredClone(mockData); + + return useResponseSuccess(listData); +}); diff --git a/apps/backend-mock/api/system/menu/list.ts b/apps/backend-mock/api/system/menu/list.ts new file mode 100644 index 00000000..ce96bb14 --- /dev/null +++ b/apps/backend-mock/api/system/menu/list.ts @@ -0,0 +1,13 @@ +import { eventHandler } from 'h3'; +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { MOCK_MENU_LIST } from '~/utils/mock-data'; +import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + + return useResponseSuccess(MOCK_MENU_LIST); +}); diff --git a/apps/backend-mock/api/system/menu/name-exists.ts b/apps/backend-mock/api/system/menu/name-exists.ts new file mode 100644 index 00000000..7d5551b3 --- /dev/null +++ b/apps/backend-mock/api/system/menu/name-exists.ts @@ -0,0 +1,29 @@ +import { eventHandler, getQuery } from 'h3'; +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { MOCK_MENU_LIST } from '~/utils/mock-data'; +import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; + +const namesMap: Record = {}; + +function getNames(menus: any[]) { + menus.forEach((menu) => { + namesMap[menu.name] = String(menu.id); + if (menu.children) { + getNames(menu.children); + } + }); +} +getNames(MOCK_MENU_LIST); + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + const { id, name } = getQuery(event); + + return (name as string) in namesMap && + (!id || namesMap[name as string] !== String(id)) + ? useResponseSuccess(true) + : useResponseSuccess(false); +}); diff --git a/apps/backend-mock/api/system/menu/path-exists.ts b/apps/backend-mock/api/system/menu/path-exists.ts new file mode 100644 index 00000000..f3c3be99 --- /dev/null +++ b/apps/backend-mock/api/system/menu/path-exists.ts @@ -0,0 +1,29 @@ +import { eventHandler, getQuery } from 'h3'; +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { MOCK_MENU_LIST } from '~/utils/mock-data'; +import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; + +const pathMap: Record = { '/': 0 }; + +function getPaths(menus: any[]) { + menus.forEach((menu) => { + pathMap[menu.path] = String(menu.id); + if (menu.children) { + getPaths(menu.children); + } + }); +} +getPaths(MOCK_MENU_LIST); + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + const { id, path } = getQuery(event); + + return (path as string) in pathMap && + (!id || pathMap[path as string] !== String(id)) + ? useResponseSuccess(true) + : useResponseSuccess(false); +}); diff --git a/apps/backend-mock/api/system/role/list.ts b/apps/backend-mock/api/system/role/list.ts new file mode 100644 index 00000000..bad29a51 --- /dev/null +++ b/apps/backend-mock/api/system/role/list.ts @@ -0,0 +1,84 @@ +import { faker } from '@faker-js/faker'; +import { eventHandler, getQuery } from 'h3'; +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { getMenuIds, MOCK_MENU_LIST } from '~/utils/mock-data'; +import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response'; + +const formatterCN = new Intl.DateTimeFormat('zh-CN', { + timeZone: 'Asia/Shanghai', + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', +}); + +const menuIds = getMenuIds(MOCK_MENU_LIST); + +function generateMockDataList(count: number) { + const dataList = []; + + for (let i = 0; i < count; i++) { + const dataItem: Record = { + id: faker.string.uuid(), + name: faker.commerce.product(), + status: faker.helpers.arrayElement([0, 1]), + createTime: formatterCN.format( + faker.date.between({ from: '2022-01-01', to: '2025-01-01' }), + ), + permissions: faker.helpers.arrayElements(menuIds), + remark: faker.lorem.sentence(), + }; + + dataList.push(dataItem); + } + + return dataList; +} + +const mockData = generateMockDataList(100); + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + + const { + page = 1, + pageSize = 20, + name, + id, + remark, + startTime, + endTime, + status, + } = getQuery(event); + let listData = structuredClone(mockData); + if (name) { + listData = listData.filter((item) => + item.name.toLowerCase().includes(String(name).toLowerCase()), + ); + } + if (id) { + listData = listData.filter((item) => + item.id.toLowerCase().includes(String(id).toLowerCase()), + ); + } + if (remark) { + listData = listData.filter((item) => + item.remark?.toLowerCase()?.includes(String(remark).toLowerCase()), + ); + } + if (startTime) { + listData = listData.filter((item) => item.createTime >= startTime); + } + if (endTime) { + listData = listData.filter((item) => item.createTime <= endTime); + } + if (['0', '1'].includes(status as string)) { + listData = listData.filter((item) => item.status === Number(status)); + } + return usePageResponseSuccess(page as string, pageSize as string, listData); +}); diff --git a/apps/backend-mock/api/system/user/list.ts b/apps/backend-mock/api/system/user/list.ts new file mode 100644 index 00000000..21d44c8b --- /dev/null +++ b/apps/backend-mock/api/system/user/list.ts @@ -0,0 +1,85 @@ +import { faker } from '@faker-js/faker'; +import { eventHandler, getQuery } from 'h3'; +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response'; + +const formatterCN = new Intl.DateTimeFormat('zh-CN', { + timeZone: 'Asia/Shanghai', + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', +}); + +function generateMockDataList(count: number) { + const dataList = []; + + for (let i = 0; i < count; i++) { + const dataItem: Record = { + id: faker.string.uuid(), + name: faker.commerce.product(), + status: faker.helpers.arrayElement([0, 1]), + createTime: formatterCN.format( + faker.date.between({ from: '2022-01-01', to: '2025-01-01' }), + ), + deptId: faker.string.uuid(), + remark: faker.lorem.sentence(), + }; + + dataList.push(dataItem); + } + + return dataList; +} + +const mockData = generateMockDataList(100); + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + + const { + page = 1, + pageSize = 20, + name, + id, + remark, + startTime, + endTime, + deptId, + status, + } = getQuery(event); + let listData = structuredClone(mockData); + if (name) { + listData = listData.filter((item) => + item.name.toLowerCase().includes(String(name).toLowerCase()), + ); + } + if (id) { + listData = listData.filter((item) => + item.id.toLowerCase().includes(String(id).toLowerCase()), + ); + } + if (remark) { + listData = listData.filter((item) => + item.remark?.toLowerCase()?.includes(String(remark).toLowerCase()), + ); + } + if (startTime) { + listData = listData.filter((item) => item.createTime >= startTime); + } + if (endTime) { + listData = listData.filter((item) => item.createTime <= endTime); + } + if (['0', '1'].includes(status as string)) { + listData = listData.filter((item) => item.status === Number(status)); + } + if (deptId) { + listData = listData.filter((item) => item.deptId === deptId); + } + return usePageResponseSuccess(page as string, pageSize as string, listData); +}); diff --git a/apps/backend-mock/api/timezone/getTimezone.ts b/apps/backend-mock/api/timezone/getTimezone.ts new file mode 100644 index 00000000..0cbcb6ec --- /dev/null +++ b/apps/backend-mock/api/timezone/getTimezone.ts @@ -0,0 +1,12 @@ +import { eventHandler } from 'h3'; +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; +import { getTimezone } from '~/utils/timezone-utils'; + +export default eventHandler((event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + return useResponseSuccess(getTimezone()); +}); diff --git a/apps/backend-mock/api/timezone/getTimezoneOptions.ts b/apps/backend-mock/api/timezone/getTimezoneOptions.ts new file mode 100644 index 00000000..6c241864 --- /dev/null +++ b/apps/backend-mock/api/timezone/getTimezoneOptions.ts @@ -0,0 +1,11 @@ +import { eventHandler } from 'h3'; +import { TIME_ZONE_OPTIONS } from '~/utils/mock-data'; +import { useResponseSuccess } from '~/utils/response'; + +export default eventHandler(() => { + const data = TIME_ZONE_OPTIONS.map((o) => ({ + label: `${o.timezone} (GMT${o.offset >= 0 ? `+${o.offset}` : o.offset})`, + value: o.timezone, + })); + return useResponseSuccess(data); +}); diff --git a/apps/backend-mock/api/timezone/setTimezone.ts b/apps/backend-mock/api/timezone/setTimezone.ts new file mode 100644 index 00000000..851e4351 --- /dev/null +++ b/apps/backend-mock/api/timezone/setTimezone.ts @@ -0,0 +1,26 @@ +import { eventHandler, readBody, setResponseStatus } from 'h3'; +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { TIME_ZONE_OPTIONS } from '~/utils/mock-data'; +import { + unAuthorizedResponse, + useResponseError, + useResponseSuccess, +} from '~/utils/response'; +import { setTimezone } from '~/utils/timezone-utils'; + +export default eventHandler(async (event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + const body = await readBody<{ timezone?: unknown }>(event); + const timezone = + typeof body?.timezone === 'string' ? body.timezone : undefined; + const allowed = TIME_ZONE_OPTIONS.some((o) => o.timezone === timezone); + if (!timezone || !allowed) { + setResponseStatus(event, 400); + return useResponseError('Bad Request', 'Invalid timezone'); + } + setTimezone(timezone); + return useResponseSuccess({}); +}); diff --git a/apps/backend-mock/api/upload.ts b/apps/backend-mock/api/upload.ts new file mode 100644 index 00000000..436b63cb --- /dev/null +++ b/apps/backend-mock/api/upload.ts @@ -0,0 +1,14 @@ +import { eventHandler } from 'h3'; +import { verifyAccessToken } from '~/utils/jwt-utils'; +import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response'; + +export default eventHandler((event) => { + const userinfo = verifyAccessToken(event); + if (!userinfo) { + return unAuthorizedResponse(event); + } + return useResponseSuccess({ + url: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp', + }); + // return useResponseError("test") +}); diff --git a/apps/backend-mock/utils/timezone-utils.ts b/apps/backend-mock/utils/timezone-utils.ts new file mode 100644 index 00000000..da35f920 --- /dev/null +++ b/apps/backend-mock/utils/timezone-utils.ts @@ -0,0 +1,9 @@ +let mockTimeZone: null | string = null; + +export const setTimezone = (timeZone: string) => { + mockTimeZone = timeZone; +}; + +export const getTimezone = () => { + return mockTimeZone; +}; diff --git a/apps/web-antd/index.html b/apps/web-antd/index.html index 480eb84d..e3b9ae5c 100644 --- a/apps/web-antd/index.html +++ b/apps/web-antd/index.html @@ -12,7 +12,7 @@ content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0" /> - <%= VITE_APP_TITLE %> + %VITE_APP_TITLE% @@ -15,6 +16,7 @@ const logo = computed(() => preferences.logo.source); notifications.value.some((item) => !item.isRead), ); @@ -189,6 +192,13 @@ const showDoctorTransfer = computed(() => { }); const menus = computed(() => [ + { + handler: () => { + router.push({ name: 'Profile' }); + }, + icon: 'lucide:user', + text: $t('page.auth.profile'), + }, { handler: () => { switchAccountModalRef.value?.openModal(); @@ -218,13 +228,60 @@ function handleNoticeClear() { }); } +function markRead(id: number | string) { + const item = notifications.value.find((item) => item.id === id); + if (item) { + item.isRead = true; + } +} + +function remove(id: number | string) { + notifications.value = notifications.value.filter((item) => item.id !== id); +} + function handleMakeAll() { notifications.value.forEach((item) => (item.isRead = true)); } + +const viewAll = () => {}; + +const handleClick = (item: NotificationItem) => { + // 如果通知项有链接,点击时跳转 + if (item.link) { + navigateTo(item.link, item.query, item.state); + } +}; + +function navigateTo( + link: string, + query?: Record, + state?: Record, +) { + if (link.startsWith('http://') || link.startsWith('https://')) { + // 外部链接,在新标签页打开 + window.open(link, '_blank'); + } else { + // 内部路由链接,支持 query 参数和 state + router.push({ + path: link, + query: query || {}, + state, + }); + } +} + watch( - () => preferences.app.watermark, - async (enable) => { + () => ({ + enable: preferences.app.watermark, + content: preferences.app.watermarkContent, + isDark: isDark.value, + }), + async ({ enable, content, isDark: isDarkValue }) => { if (enable) { + const watermarkColor = isDarkValue + ? 'rgba(255, 255, 255, 0.12)' + : 'rgba(0, 0, 0, 0.12)'; + await updateWatermark({ // 这里更改水印内容 // content: `${userStore.userInfo?.nick_name || import.meta.env.VITE_APP_TITLE}`, @@ -241,23 +298,25 @@ watch(