diff --git a/.env.example b/.env.example index 7b9637efd4..4e6c72652d 100644 --- a/.env.example +++ b/.env.example @@ -37,7 +37,7 @@ CLUSTER_MODE= ################# DATABASE ################# -# postgres | msysql | mariadb | sqlite +# postgres | mysql | mariadb | sqlite DB_DIALECT=postgres DB_TABLE_PREFIX= DB_HOST=localhost diff --git a/.env.test.example b/.env.test.example index 1b79c4c9eb..67cbb7de29 100644 --- a/.env.test.example +++ b/.env.test.example @@ -19,6 +19,8 @@ DB_STORAGE=storage/db/nocobase-test.sqlite # DB_USER=nocobase # DB_PASSWORD=nocobase # DB_LOGGING=on +TZ=UTC +ENCRYPTION_FIELD_KEY="2DKJ)P+u(9bP5eF#MTdhy8ZJdfa(xT)K" ################# CACHE ################# # default is memory cache, when develop mode,code's change will be clear memory cache, so can use 'cache-manager-fs-hash' diff --git a/.github/workflows/build-pro-image.yml b/.github/workflows/build-pro-image.yml index de5761a3a2..0600375667 100644 --- a/.github/workflows/build-pro-image.yml +++ b/.github/workflows/build-pro-image.yml @@ -24,10 +24,25 @@ jobs: ports: - 4873:4873 steps: + - name: Get pro plugins + id: get-pro-plugins + run: | + if [[ "${{ github.head_ref || github.ref_name }}" == "main" ]]; then + echo "proRepos=$(echo '${{ vars.PRO_PLUGIN_REPOS }}')" >> $GITHUB_OUTPUT + else + echo "proRepos=$(echo '${{ vars.NEXT_PRO_PLUGIN_REPOS }}')" >> $GITHUB_OUTPUT + fi + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.NOCOBASE_APP_ID }} + private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }} + repositories: nocobase,pro-plugins,${{ join(fromJSON(steps.get-pro-plugins.outputs.proRepos), ',') }} + skip-token-revoke: true - name: Checkout uses: actions/checkout@v3 with: - ssh-key: ${{ secrets.SUBMODULE_SSH_KEY }} + token: ${{ steps.app-token.outputs.token }} submodules: true - name: Checkout pro-plugins uses: actions/checkout@v3 @@ -36,7 +51,7 @@ jobs: ref: main path: packages/pro-plugins fetch-depth: 0 - ssh-key: ${{ secrets.SUBMODULE_SSH_KEY }} + token: ${{ steps.app-token.outputs.token }} - run: | cd packages/pro-plugins && if git show-ref --quiet refs/remotes/origin/${{ github.head_ref || github.ref_name }}; then @@ -48,8 +63,37 @@ jobs: git checkout main fi fi + - name: Clone pro repos + shell: bash + run: | + for repo in ${{ join(fromJSON(steps.get-pro-plugins.outputs.proRepos), ' ') }} + do + git clone -b main https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/nocobase/$repo.git packages/pro-plugins/@nocobase/$repo + done + - run: | + for repo in ${{ join(fromJSON(steps.get-pro-plugins.outputs.proRepos), ' ') }} + do + cd ./packages/pro-plugins/@nocobase/$repo + if git show-ref --quiet refs/remotes/origin/${{ github.head_ref || github.ref_name }}; then + git checkout ${{ github.head_ref || github.ref_name }} + else + if git show-ref --quiet refs/remotes/origin/${{ github.event.pull_request.base.ref }}; then + git checkout ${{ github.event.pull_request.base.ref }} + else + git checkout main + fi + fi + cd ../../../../ + done - name: rm .git - run: rm -rf packages/pro-plugins/.git && git config --global user.email "you@example.com" && git config --global user.name "Your Name" && git add -A && git commit -m "tmp commit" + run: | + rm -rf packages/pro-plugins/.git + for repo in ${{ join(fromJSON(steps.get-pro-plugins.outputs.proRepos), ' ') }} + do + rm -rf packages/pro-plugins/@nocobase/$repo/.git + done + git config --global user.email "you@example.com" + git config --global user.name "Your Name" && git add -A && git commit -m "tmp commit" - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx diff --git a/.github/workflows/changelog-and-release.yml b/.github/workflows/changelog-and-release.yml new file mode 100644 index 0000000000..7b9b6f2f40 --- /dev/null +++ b/.github/workflows/changelog-and-release.yml @@ -0,0 +1,82 @@ +name: Write changelog and create release + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + workflow_dispatch: + inputs: + version: + type: choice + description: Please choose a version + options: + - beta + - alpha + default: beta + push: + tags: + - 'v*-beta' + +jobs: + write-changelog-and-release: + runs-on: ubuntu-latest + steps: + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.NOCOBASE_APP_ID }} + private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }} + repositories: nocobase,pro-plugins,${{ join(fromJSON(vars.PRO_PLUGIN_REPOS), ',') }} + skip-token-revoke: true + - name: Get GitHub App User ID + id: get-user-id + run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + - name: Checkout + uses: actions/checkout@v4 + with: + repository: nocobase/nocobase + ref: main + token: ${{ steps.app-token.outputs.token }} + persist-credentials: true + fetch-depth: 0 + - name: Checkout pro-plugins + uses: actions/checkout@v4 + with: + repository: nocobase/pro-plugins + path: packages/pro-plugins + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} + persist-credentials: true + - name: Clone pro repos + shell: bash + run: | + for repo in ${{ join(fromJSON(vars.PRO_PLUGIN_REPOS), ' ') }} + do + git clone -b main https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/nocobase/$repo.git packages/pro-plugins/@nocobase/$repo + done + - name: Set user + run: | + git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]' + git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>' + - name: Set Node.js 18 + uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Install dependencies + run: yarn install --frozen-lockfile + - name: Run script + shell: bash + run: | + node scripts/release/changelogAndRelease.js --ver ${{ inputs.version }} + env: + PRO_PLUGIN_REPOS: ${{ vars.PRO_PLUGIN_REPOS }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} + - name: Commit and push + run: | + git pull origin main + git add . + git commit -m "docs: update changelogs" + git push origin main diff --git a/.github/workflows/get-nocobase-app-token.yml b/.github/workflows/get-nocobase-app-token.yml new file mode 100644 index 0000000000..1c3b51dc97 --- /dev/null +++ b/.github/workflows/get-nocobase-app-token.yml @@ -0,0 +1,39 @@ +name: Get nocobase app github token + +on: + workflow_call: + outputs: + token: + value: ${{ jobs.get-app-token.outputs.token }} + user-id: + value: ${{ jobs.get-app-token.outputs.user-id }} + app-slug: + value: ${{ jobs.get-app-token.outputs.app-slug }} + +jobs: + get-app-token: + runs-on: ubuntu-latest + outputs: + token: ${{ steps.encrypt-token.outputs.token }} + app-slug: ${{ steps.app-token.outputs.app-slug }} + user-id: ${{ steps.get-user-id.outputs.user-id }} + steps: + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.NOCOBASE_APP_ID }} + private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }} + repositories: nocobase,pro-plugins,${{ join(fromJSON(vars.PRO_PLUGIN_REPOS), ',') }} + skip-token-revoke: true + - name: Encrypt token + id: encrypt-token + shell: bash + run: | + APP_TOKEN=${{ steps.app-token.outputs.token }}; + ENCRYPTED_SECRET=$(echo -n "$APP_TOKEN" | openssl enc -aes-256-cbc -pbkdf2 -salt -k "${{ secrets.APP_TOKEN_ENCRYPTION_PASSWORD }}" | base64 -w 0); + echo "token=$ENCRYPTED_SECRET" >> $GITHUB_OUTPUT + - name: Get GitHub App User ID + id: get-user-id + run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/manual-build-pro-image.yml b/.github/workflows/manual-build-pro-image.yml index fbddf5f7a4..c408036dfe 100644 --- a/.github/workflows/manual-build-pro-image.yml +++ b/.github/workflows/manual-build-pro-image.yml @@ -25,11 +25,26 @@ jobs: ports: - 4873:4873 steps: + - name: Get pro plugins + id: get-pro-plugins + run: | + if [[ "${{ github.head_ref || github.ref_name }}" == "main" ]]; then + echo "proRepos=$(echo '${{ vars.PRO_PLUGIN_REPOS }}')" >> $GITHUB_OUTPUT + else + echo "proRepos=$(echo '${{ vars.NEXT_PRO_PLUGIN_REPOS }}')" >> $GITHUB_OUTPUT + fi + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.NOCOBASE_APP_ID }} + private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }} + repositories: nocobase,pro-plugins,${{ join(fromJSON(steps.get-pro-plugins.outputs.proRepos), ',') }} + skip-token-revoke: true - name: Checkout uses: actions/checkout@v3 with: ref: ${{ github.event.inputs.base_branch }} - ssh-key: ${{ secrets.SUBMODULE_SSH_KEY }} + token: ${{ steps.app-token.outputs.token }} submodules: true - name: Set PR branch id: set_pro_pr_branch @@ -43,9 +58,23 @@ jobs: repository: nocobase/pro-plugins path: packages/pro-plugins ref: ${{ steps.set_pro_pr_branch.outputs.pr_branch || 'main' }} - ssh-key: ${{ secrets.SUBMODULE_SSH_KEY }} + token: ${{ steps.app-token.outputs.token }} + - name: Clone pro repos + shell: bash + run: | + for repo in ${{ join(fromJSON(steps.get-pro-plugins.outputs.proRepos), ' ') }} + do + git clone -b ${{ steps.set_pro_pr_branch.outputs.pr_branch || 'main' }} https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/nocobase/$repo.git packages/pro-plugins/@nocobase/$repo + done - name: rm .git - run: rm -rf packages/pro-plugins/.git && git config --global user.email "you@example.com" && git config --global user.name "Your Name" && git add -A && git commit -m "tmp commit" + run: | + rm -rf packages/pro-plugins/.git + for repo in ${{ join(fromJSON(steps.get-pro-plugins.outputs.proRepos), ' ') }} + do + rm -rf packages/pro-plugins/@nocobase/$repo/.git + done + git config --global user.email "you@example.com" + git config --global user.name "Your Name" && git add -A && git commit -m "tmp commit" - name: Set up QEMU uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx diff --git a/.github/workflows/manual-build-pro-plugin-image.yml b/.github/workflows/manual-build-pro-plugin-image.yml new file mode 100644 index 0000000000..7990add6ff --- /dev/null +++ b/.github/workflows/manual-build-pro-plugin-image.yml @@ -0,0 +1,112 @@ +name: Build Pro Plugin Docker Image + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + workflow_dispatch: + inputs: + pro_plugin: + description: 'Please enter a pro plugin name' + required: true + pr_number: + description: 'Please enter the pr number of pro plugin repository' + required: false +jobs: + build-and-push: + runs-on: ubuntu-latest + services: + verdaccio: + image: verdaccio/verdaccio:latest + ports: + - 4873:4873 + steps: + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.NOCOBASE_APP_ID }} + private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }} + repositories: nocobase,pro-plugins,${{ join(fromJSON(vars.NEXT_PRO_PLUGIN_REPOS), ',') }},${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ',') }} + skip-token-revoke: true + - name: Checkout nocobase/nocobase + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref || github.ref_name }} + fetch-depth: 0 + - name: Checkout plugin + uses: actions/checkout@v3 + with: + ref: main + token: ${{ steps.app-token.outputs.token }} + repository: nocobase/plugin-${{ inputs.pro_plugin }} + path: packages/pro-plugins/@nocobase/plugin-${{ inputs.pro_plugin }} + - name: Checkout pr + shell: bash + run: | + cd ./packages/pro-plugins/@nocobase/plugin-${{ inputs.pro_plugin }} + gh pr checkout ${{ inputs.pr_number }} + cd ../../../../ + env: + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + - name: rm .git + run: rm -rf packages/pro-plugins/@nocobase/plugin-${{ inputs.pro_plugin }}/.git && git config --global user.email "you@example.com" && git config --global user.name "Your Name" && git add -A && git commit -m "tmp commit" + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + with: + driver-opts: network=host + - name: Login to Aliyun Container Registry + uses: docker/login-action@v2 + with: + registry: ${{ secrets.ALI_DOCKER_REGISTRY }} + username: ${{ secrets.ALI_DOCKER_USERNAME }} + password: ${{ secrets.ALI_DOCKER_PASSWORD }} + - name: Set tags + id: set-tags + run: | + echo "::set-output name=tags::${{ secrets.ALI_DOCKER_REGISTRY }}/nocobase/nocobase:pr-${{ inputs.pr_number }}-${{ inputs.pro_plugin }}" + - name: IMAGE_TAG + env: + IMAGE_TAG: pr-${{ inputs.pr_number }} + run: | + echo $IMAGE_TAG + - name: Set variables + run: | + target_directory="./packages/pro-plugins/@nocobase" + subdirectories=$(find "$target_directory" -mindepth 1 -maxdepth 1 -type d -exec basename {} \; | tr '\n' ' ') + trimmed_variable=$(echo "$subdirectories" | xargs) + packageNames="@nocobase/${trimmed_variable// / @nocobase/}" + pluginNames="${trimmed_variable//plugin-/}" + BEFORE_PACK_NOCOBASE="yarn add $packageNames -W" + APPEND_PRESET_LOCAL_PLUGINS="${pluginNames// /,}" + echo "var1=$BEFORE_PACK_NOCOBASE" >> $GITHUB_OUTPUT + echo "var2=$APPEND_PRESET_LOCAL_PLUGINS" >> $GITHUB_OUTPUT + id: vars + - name: Build and push + uses: docker/build-push-action@v3 + with: + context: . + file: Dockerfile + build-args: | + VERDACCIO_URL=http://localhost:4873/ + COMMIT_HASH=${GITHUB_SHA} + PLUGINS_DIRS=pro-plugins + BEFORE_PACK_NOCOBASE=${{ steps.vars.outputs.var1 }} + APPEND_PRESET_LOCAL_PLUGINS=${{ steps.vars.outputs.var2 }} + push: true + tags: ${{ steps.set-tags.outputs.tags }} + - name: Deploy NocoBase + env: + IMAGE_TAG: pr-${{ inputs.pr_number }} + run: | + echo $IMAGE_TAG + export APP_NAME=$(echo $IMAGE_TAG | cut -d ":" -f 2)-${{ inputs.pro_plugin }} + echo $APP_NAME + curl --retry 2 --location --request POST "${{secrets.NOCOBASE_DEPLOY_HOST}}$APP_NAME" \ + --header 'Content-Type: application/json' \ + -d "{ + \"tag\": \"$APP_NAME\", + \"dialect\": \"postgres\" + }" diff --git a/.github/workflows/manual-release.yml b/.github/workflows/manual-release.yml index 2c5378b214..51cf9efa6b 100644 --- a/.github/workflows/manual-release.yml +++ b/.github/workflows/manual-release.yml @@ -12,14 +12,66 @@ on: type: boolean jobs: - push-commit: + pre-merge-main-into-next: runs-on: ubuntu-latest + strategy: + matrix: + repo: + - 'nocobase' + - 'pro-plugins' + - ${{ fromJSON(vars.PRO_PLUGIN_REPOS) }} + - ${{ fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS) }} steps: + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.NOCOBASE_APP_ID }} + private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }} + repositories: nocobase,pro-plugins,${{ join(fromJSON(vars.PRO_PLUGIN_REPOS), ',') }},${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ',') }} + skip-token-revoke: true + - name: Get GitHub App User ID + id: get-user-id + run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} + - name: Checkout + uses: actions/checkout@v4 + with: + # ref: 'main' + repository: nocobase/${{ matrix.repo }} + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} + - name: main -> next (nocobase/${{ matrix.repo }}) + run: | + git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]' + git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>' + git checkout main + git pull origin main + git checkout next + git merge main + git push origin next --tags --atomic + update-version: + needs: + - pre-merge-main-into-next + runs-on: ubuntu-latest + steps: + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.NOCOBASE_APP_ID }} + private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }} + repositories: nocobase,pro-plugins,${{ join(fromJSON(vars.PRO_PLUGIN_REPOS), ',') }},${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ',') }} + skip-token-revoke: true + - name: Get GitHub App User ID + id: get-user-id + run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} - name: Checkout uses: actions/checkout@v4 with: repository: nocobase/nocobase - ssh-key: ${{ secrets.NOCOBASE_DEPLOY_KEY }} + token: ${{ steps.app-token.outputs.token }} persist-credentials: true fetch-depth: 0 - name: Checkout pro-plugins @@ -28,43 +80,15 @@ jobs: repository: nocobase/pro-plugins path: packages/pro-plugins fetch-depth: 0 - ssh-key: ${{ secrets.PRO_PLUGINS_DEPLOY_KEY }} + token: ${{ steps.app-token.outputs.token }} persist-credentials: true - - name: main -> next(nocobase) + - name: Clone pro repos + shell: bash run: | - git config --global user.email "actions@github.com" - git config --global user.name "GitHub Actions Bot" - git checkout main - git pull origin main - git checkout next - git merge main - git push origin next - - name: main -> next(pro-plugins) - run: | - cd ./packages/pro-plugins - git checkout main - git pull origin main - git checkout next - git merge main - git push origin next - - name: push pro plugins(next) - continue-on-error: true - uses: ad-m/github-push-action@master - with: - ssh: true - branch: next - directory: packages/pro-plugins - repository: nocobase/pro-plugins - tags: true - atomic: true - - name: push nocobase(next) - uses: ad-m/github-push-action@master - with: - branch: next - ssh: true - repository: nocobase/nocobase - tags: true - atomic: true + for repo in ${{ join(fromJSON(vars.PRO_PLUGIN_REPOS), ' ') }} ${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ' ') }} + do + git clone -b main https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/nocobase/$repo.git packages/pro-plugins/@nocobase/$repo + done - name: Set Node.js 18 uses: actions/setup-node@v3 with: @@ -72,67 +96,43 @@ jobs: - name: Install Lerna run: npm install -g lerna@4 auto-changelog@2 - name: Run release.sh + shell: bash run: | cd ./packages/pro-plugins git checkout main + for repo in ${{ join(fromJSON(vars.PRO_PLUGIN_REPOS), ' ') }} ${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ' ') }} + do + echo "@nocobase/$repo" >> .git/info/exclude + done + echo "$(<.git/info/exclude )" cd ./../.. git checkout main - git config --global user.email "actions@github.com" - git config --global user.name "GitHub Actions Bot" - echo "packages/pro-plugins/" >> .git/info/exclude + git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]' + git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>' + echo "packages/pro-plugins" >> .git/info/exclude bash release.sh $IS_FEAT env: IS_FEAT: ${{ inputs.is_feat && '--is-feat' || '' }} - - name: push pro plugins - continue-on-error: true - uses: ad-m/github-push-action@master - with: - ssh: true - branch: main - directory: packages/pro-plugins - repository: nocobase/pro-plugins - tags: true - atomic: true - - name: push nocobase - uses: ad-m/github-push-action@master - with: - branch: main - ssh: true - repository: nocobase/nocobase - tags: true - atomic: true - - name: main -> next + PRO_PLUGIN_REPOS: ${{ vars.PRO_PLUGIN_REPOS }} + CUSTOM_PRO_PLUGIN_REPOS: ${{ vars.CUSTOM_PRO_PLUGIN_REPOS }} + - name: Push and merge into next run: | - git config --global user.email "actions@github.com" - git config --global user.name "GitHub Actions Bot" - git checkout main - git pull origin main + for repo in ${{ join(fromJSON(vars.PRO_PLUGIN_REPOS), ' ') }} ${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ' ') }} + do + cd ./packages/pro-plugins/@nocobase/$repo + git push origin main --atomic --tags + git checkout next + git merge -X ours main --no-edit + git push origin next --tags --atomic + cd ../../../../ + done + cd ./packages/pro-plugins + git push origin main --atomic --tags git checkout next git merge -X ours main --no-edit - git push origin next - - name: main -> next - run: | - cd ./packages/pro-plugins - git checkout main - git pull origin main + git push origin next --tags --atomic + cd ../../ + git push origin main --atomic --tags git checkout next git merge -X ours main --no-edit - git push origin next - - name: push pro plugins - continue-on-error: true - uses: ad-m/github-push-action@master - with: - ssh: true - branch: next - directory: packages/pro-plugins - repository: nocobase/pro-plugins - tags: true - atomic: true - - name: push nocobase - uses: ad-m/github-push-action@master - with: - branch: next - ssh: true - repository: nocobase/nocobase - tags: true - atomic: true \ No newline at end of file + git push origin next --tags --atomic diff --git a/.github/workflows/release-next.yml b/.github/workflows/release-next.yml index 1638cf45fd..61264465d7 100644 --- a/.github/workflows/release-next.yml +++ b/.github/workflows/release-next.yml @@ -12,10 +12,23 @@ jobs: runs-on: ubuntu-latest container: node:18 steps: + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.NOCOBASE_APP_ID }} + private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }} + repositories: nocobase,pro-plugins,${{ join(fromJSON(vars.NEXT_PRO_PLUGIN_REPOS), ',') }},${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ',') }} + skip-token-revoke: true + - name: Get GitHub App User ID + id: get-user-id + run: echo "user-id=$(gh api "/users/${{ steps.app-token.outputs.app-slug }}[bot]" --jq .id)" >> "$GITHUB_OUTPUT" + env: + GH_TOKEN: ${{ steps.app-token.outputs.token }} - name: Checkout uses: actions/checkout@v4 with: ref: next + fetch-depth: 0 - name: Send curl request and parse response env: PKG_USERNAME: ${{ secrets.PKG_USERNAME }} @@ -64,8 +77,8 @@ jobs: - name: publish npmjs.org continue-on-error: true run: | - git config --global user.email "test@mail.com" - git config --global user.name "test" + git config --global user.name '${{ steps.app-token.outputs.app-slug }}[bot]' + git config --global user.email '${{ steps.get-user-id.outputs.user-id }}+${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com>' git config --global --add safe.directory /__w/nocobase/nocobase npm config set access public npm config set registry https://registry.npmjs.org/ @@ -87,9 +100,16 @@ jobs: repository: nocobase/pro-plugins path: packages/pro-plugins ref: next - ssh-key: ${{ secrets.SUBMODULE_SSH_KEY }} + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} + - name: Clone pro repos + shell: bash + run: | + for repo in ${{ join(fromJSON(vars.NEXT_PRO_PLUGIN_REPOS), ' ') }} ${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ' ') }} + do + git clone -b next https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/nocobase/$repo.git packages/pro-plugins/@nocobase/$repo + done - name: Build Pro plugins - continue-on-error: true run: | yarn config set registry https://registry.npmjs.org/ yarn install @@ -99,10 +119,36 @@ jobs: run: | git reset --hard npm config set //pkg.nocobase.com/:_authToken=${{ env.PKG_NOCOBASE_TOKEN }} - yarn release:force --no-verify-access --no-git-reset --registry https://pkg.nocobase.com --dist-tag=next + # yarn release:force --no-verify-access --no-git-reset --registry https://pkg.nocobase.com --dist-tag=next - name: publish pkg-src.nocobase.com run: | git reset --hard bash generate-npmignore.sh ignore-src npm config set //pkg-src.nocobase.com/:_authToken=${{ env.PKG_SRC_NOCOBASE_TOKEN }} yarn release:force --no-verify-access --no-git-reset --registry https://pkg-src.nocobase.com --dist-tag=next + - name: Tag + run: | + git reset --hard HEAD~ + git tag v${{ env.NEWVERSION }} + git push origin v${{ env.NEWVERSION }} + cd ./packages/pro-plugins + git reset --hard + git tag v${{ env.NEWVERSION }} + git push origin v${{ env.NEWVERSION }} + cd ../../ + for repo in ${{ join(fromJSON(vars.NEXT_PRO_PLUGIN_REPOS), ' ') }} ${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ' ') }} + do + cd ./packages/pro-plugins/@nocobase/$repo + git reset --hard + git tag v${{ env.NEWVERSION }} + git push origin v${{ env.NEWVERSION }} + cd ../../../../ + done + - name: Run release script + shell: bash + run: | + git fetch + node scripts/release/changelogAndRelease.js --ver alpha --test + env: + PRO_PLUGIN_REPOS: ${{ vars.NEXT_PRO_PLUGIN_REPOS }} + GH_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9c102d6a28..7e92a8dcdc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,6 +14,13 @@ jobs: runs-on: ubuntu-latest container: node:18 steps: + - uses: actions/create-github-app-token@v1 + id: app-token + with: + app-id: ${{ vars.NOCOBASE_APP_ID }} + private-key: ${{ secrets.NOCOBASE_APP_PRIVATE_KEY }} + repositories: nocobase,pro-plugins,${{ join(fromJSON(vars.PRO_PLUGIN_REPOS), ',') }},${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ',') }} + skip-token-revoke: true - name: Checkout uses: actions/checkout@v3 - name: Send curl request and parse response @@ -60,9 +67,15 @@ jobs: with: repository: nocobase/pro-plugins path: packages/pro-plugins - ssh-key: ${{ secrets.SUBMODULE_SSH_KEY }} + token: ${{ steps.app-token.outputs.token }} + - name: Clone pro repos + shell: bash + run: | + for repo in ${{ join(fromJSON(vars.PRO_PLUGIN_REPOS), ' ') }} ${{ join(fromJSON(vars.CUSTOM_PRO_PLUGIN_REPOS), ' ') }} + do + git clone -b main https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/nocobase/$repo.git packages/pro-plugins/@nocobase/$repo + done - name: Build Pro plugins - continue-on-error: true run: | yarn config set registry https://registry.npmjs.org/ yarn install diff --git a/CHANGELOG.md b/CHANGELOG.md index f75b7f6293..c751f35b34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,256 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). +## [v1.3.19-beta](https://github.com/nocobase/nocobase/compare/v1.3.18-beta...v1.3.19-beta) - 2024-09-08 + +### 🐛 Bug Fixes + +- **[client]** Fix URL anomalies caused by using popups together with Link buttons ([#5219](https://github.com/nocobase/nocobase/pull/5219)) by @zhangzhonghe + +## [v1.3.18-beta](https://github.com/nocobase/nocobase/compare/v1.3.17-beta...v1.3.18-beta) - 2024-09-08 + +### 🐛 Bug Fixes + +- **[Collection field: Many to many (array)]** Fix the error when deleting a collection contains m2m array fields ([#5231](https://github.com/nocobase/nocobase/pull/5231)) by @2013xile + +## [v1.3.17-beta](https://github.com/nocobase/nocobase/compare/v1.3.16-beta...v1.3.17-beta) - 2024-09-07 + +### 🎉 New Features + +- **[client]** Supports configuration of linkage rules in sub-forms and sub-forms. ([#5159](https://github.com/nocobase/nocobase/pull/5159)) by @zhangzhonghe + +### 🚀 Improvements + +- **[client]** + - default time for display is 00:00:00 ([#5226](https://github.com/nocobase/nocobase/pull/5226)) by @chenos + + - plugins can also be enabled when the plugin dependency version is inconsistent ([#5225](https://github.com/nocobase/nocobase/pull/5225)) by @chenos + +- **[server]** provide more user-friendly application-level error messages ([#5220](https://github.com/nocobase/nocobase/pull/5220)) by @chenos + +### 🐛 Bug Fixes + +- **[client]** Fix the "Maximum call stack size exceeded" error that occurs in the details block ([#5228](https://github.com/nocobase/nocobase/pull/5228)) by @zhangzhonghe + +- **[Collection field: Many to many (array)]** Fix the error where setting a field of `uid` type as target key for a many to many (array) field ([#5229](https://github.com/nocobase/nocobase/pull/5229)) by @2013xile + +- **[UI schema storage]** Fix the issue that member roles clicking the button reported no permission ([#5206](https://github.com/nocobase/nocobase/pull/5206)) by @zhangzhonghe + +- **[Workflow]** Fix trigger type column showing wrong text after new workflow created ([#5222](https://github.com/nocobase/nocobase/pull/5222)) by @mytharcher + +- **[Users]** Remove phone format validation when editing user phones in user management ([#5221](https://github.com/nocobase/nocobase/pull/5221)) by @2013xile + +## [v1.3.16-beta](https://github.com/nocobase/nocobase/compare/v1.3.15-beta...v1.3.16-beta) - 2024-09-06 + +### 🚀 Improvements + +- **[client]** + - Placeholder added when the user has UI configuration permissions but no view permissions for the collection ([#5208](https://github.com/nocobase/nocobase/pull/5208)) by @katherinehhh + + - Display system title when logo is missing. ([#5175](https://github.com/nocobase/nocobase/pull/5175)) by @maoyutofu + +- **[Authentication]** support line break in system title ([#5211](https://github.com/nocobase/nocobase/pull/5211)) by @chenos + +- **[Workflow: SQL node]** Change result data structure of SQL node to only contains data. ([#5189](https://github.com/nocobase/nocobase/pull/5189)) by @mytharcher +Reference: [SQL Operation](https://docs.nocobase.com/handbook/workflow/nodes/sql) +- **[Access control]** Make the `Permissions` Tab pannel of the `Users & Permissions` configuration page expandable. ([#5216](https://github.com/nocobase/nocobase/pull/5216)) by @zhangzhonghe +Reference: [Development Guide](https://docs.nocobase.com/handbook/acl#development-guide) +- **[Action: Batch edit]** batch updated and batch edit, change 'All' to 'Entire collection" ([#5200](https://github.com/nocobase/nocobase/pull/5200)) by @katherinehhh + +### 🐛 Bug Fixes + +- **[client]** + - component display error when switching assignment types in linkage rules ([#5180](https://github.com/nocobase/nocobase/pull/5180)) by @katherinehhh + + - Fix an issue where using variables in data scope reported an error. ([#5195](https://github.com/nocobase/nocobase/pull/5195)) by @zhangzhonghe + + - issue with custom request refreshDataBlockRequest ([#5188](https://github.com/nocobase/nocobase/pull/5188)) by @katherinehhh + +- **[Data visualization]** Fixed the issue of getting wrong value when aggregating select fields ([#5214](https://github.com/nocobase/nocobase/pull/5214)) by @2013xile + +- **[Data source manager]** Fixed incorrect `rowKey` of the datasource table in `Users & Permissions` page ([#5215](https://github.com/nocobase/nocobase/pull/5215)) by @gchust + +- **[Workflow: HTTP request node]** Fix error when using non-string variable in request parameters. ([#5204](https://github.com/nocobase/nocobase/pull/5204)) by @mytharcher + +- **[Collection field: Formula]** fix formula field serve test ([#5197](https://github.com/nocobase/nocobase/pull/5197)) by @katherinehhh + +- **[App backup & restore (deprecated)]** fix test case errors ([#5201](https://github.com/nocobase/nocobase/pull/5201)) by @chenos + +- **[Data source: REST API]** + - collection name should be disabled in rest-api collection by @katherinehhh + + - Rest api locale improve by @katherinehhh + +## [v1.3.15-beta](https://github.com/nocobase/nocobase/compare/v1.3.14-beta...v1.3.15-beta) - 2024-09-04 + +### 🐛 Bug Fixes + +- **[Workflow]** Fix missed fields in workflow variables. ([#5187](https://github.com/nocobase/nocobase/pull/5187)) by @mytharcher + +- **[Collection field: Markdown(Vditor)]** issue with markdown(Vditor) ([#5176](https://github.com/nocobase/nocobase/pull/5176)) by @katherinehhh + +## [v1.3.14-beta](https://github.com/nocobase/nocobase/compare/v1.3.13-beta...v1.3.14-beta) - 2024-09-03 + +### 🎉 New Features + +- **[client]** Add support for many-to-many association fields. ([#5178](https://github.com/nocobase/nocobase/pull/5178)) by @zhangzhonghe + +### 🚀 Improvements + +- **[Action: Custom request]** remove linkageRule for custom request in create form ([#5179](https://github.com/nocobase/nocobase/pull/5179)) by @katherinehhh + +### 🐛 Bug Fixes + +- **[Collection field: Formula]** formula field adaptation time field ([#5168](https://github.com/nocobase/nocobase/pull/5168)) by @katherinehhh + +## [v1.3.13-beta](https://github.com/nocobase/nocobase/compare/v1.3.12-beta...v1.3.13-beta) - 2024-09-03 + +### 🐛 Bug Fixes + +- **[Action: Export records]** Fixed incorrect export of relational data ([#5170](https://github.com/nocobase/nocobase/pull/5170)) by @chareice + +## [v1.3.12-beta](https://github.com/nocobase/nocobase/compare/v1.3.11-beta...v1.3.12-beta) - 2024-09-01 + +### Merged + +- fix(mobile): fix permission [`#5163`](https://github.com/nocobase/nocobase/pull/5163) + +### Commits + +- chore(versions): 😊 publish v1.3.12-beta [`774c296`](https://github.com/nocobase/nocobase/commit/774c2961d47aa17d1a9da7a595bb070f34aee11b) +- chore: update changelog [`7f9a116`](https://github.com/nocobase/nocobase/commit/7f9a11698f3126257529ce4a91670239900f2ec3) +- chore: update e2e test [`49db3e4`](https://github.com/nocobase/nocobase/commit/49db3e490821cd59aaba2f58ed2bb78051a86ad9) + +## [v1.3.11-beta](https://github.com/nocobase/nocobase/compare/v1.3.10-beta...v1.3.11-beta) - 2024-08-31 + +### Commits + +- chore(versions): 😊 publish v1.3.11-beta [`517e199`](https://github.com/nocobase/nocobase/commit/517e199ed7a8e7dc81a06c50389ef41b6891b133) +- chore: update changelog [`373f517`](https://github.com/nocobase/nocobase/commit/373f51773b772886cc8db3cb50184562113c62eb) + +## [v1.3.10-beta](https://github.com/nocobase/nocobase/compare/v1.3.9-beta...v1.3.10-beta) - 2024-08-31 + +### Merged + +- fix: issue with association select data scope linkage in sub-table [`#5160`](https://github.com/nocobase/nocobase/pull/5160) +- fix: issue in data selector other block should display Markdown, not 'Add Text' [`#5161`](https://github.com/nocobase/nocobase/pull/5161) +- fix(data-vi): issue of parsing variables in filter block [`#5157`](https://github.com/nocobase/nocobase/pull/5157) +- fix(data-vi): transform the values of decimal fields from type string to number [`#5155`](https://github.com/nocobase/nocobase/pull/5155) + +### Commits + +- chore(versions): 😊 publish v1.3.10-beta [`5afac9c`](https://github.com/nocobase/nocobase/commit/5afac9cf82c78db4a7ee8ddb01a60597939ac82d) +- chore: update changelog [`6fceac1`](https://github.com/nocobase/nocobase/commit/6fceac15827a10b6fba65e98314c37f3f9e697ba) +- chore: update comment [`6e45780`](https://github.com/nocobase/nocobase/commit/6e4578056556c1c60ac721ff990a81ed37339074) + +## [v1.3.9-beta](https://github.com/nocobase/nocobase/compare/v1.3.8-beta...v1.3.9-beta) - 2024-08-29 + +### Merged + +- fix(mobile): should not force redirect to mobile page [`#5152`](https://github.com/nocobase/nocobase/pull/5152) +- chore: support year data type in mysql [`#5123`](https://github.com/nocobase/nocobase/pull/5123) + +### Commits + +- chore(versions): 😊 publish v1.3.9-beta [`bf5011f`](https://github.com/nocobase/nocobase/commit/bf5011f75a7a9b26db7fef7aa4be28d7e4e077b4) +- chore: update changelog [`b2fc646`](https://github.com/nocobase/nocobase/commit/b2fc646e5aa64d2ade03ce6fca78753cfddc26ec) + +## [v1.3.8-beta](https://github.com/nocobase/nocobase/compare/v1.3.7-beta...v1.3.8-beta) - 2024-08-29 + +### Commits + +- chore(versions): 😊 publish v1.3.8-beta [`39d021a`](https://github.com/nocobase/nocobase/commit/39d021a9aa29bef9cf15d4af546060fc4b1dbd10) +- chore: update changelog [`9f66c14`](https://github.com/nocobase/nocobase/commit/9f66c14968639d90b399d087eefac7a0c4ea4383) + +## [v1.3.7-beta](https://github.com/nocobase/nocobase/compare/v1.3.6-beta...v1.3.7-beta) - 2024-08-29 + +### Merged + +- fix: add text support handlebars [`#5150`](https://github.com/nocobase/nocobase/pull/5150) + +### Commits + +- chore(versions): 😊 publish v1.3.7-beta [`f429d13`](https://github.com/nocobase/nocobase/commit/f429d1326433e7f290e552ca91548d21b5af92e4) +- chore: update changelog [`b41e477`](https://github.com/nocobase/nocobase/commit/b41e47757ec0d1f7b0af917e25ff5b4a436042aa) + +## [v1.3.6-beta](https://github.com/nocobase/nocobase/compare/v1.3.5-beta...v1.3.6-beta) - 2024-08-29 + +### Merged + +- fix: association select data scope linkage should support edit form [`#5149`](https://github.com/nocobase/nocobase/pull/5149) + +### Commits + +- chore(versions): 😊 publish v1.3.6-beta [`39c7ce4`](https://github.com/nocobase/nocobase/commit/39c7ce4741801819b98970b95c1663915a8c3bff) +- chore: update changelog [`cfbc2a6`](https://github.com/nocobase/nocobase/commit/cfbc2a6c15a5dfb8c0684051df1cf01499ff30ac) + +## [v1.3.5-beta](https://github.com/nocobase/nocobase/compare/v1.3.4-beta...v1.3.5-beta) - 2024-08-28 + +### Merged + +- fix: association select data scope linkage should be supported in sub-form [`#5146`](https://github.com/nocobase/nocobase/pull/5146) +- fix(mobile): resovle redirect issue [`#5145`](https://github.com/nocobase/nocobase/pull/5145) +- feat(plugin-workflow): allow to delete execution in list [`#5135`](https://github.com/nocobase/nocobase/pull/5135) +- fix(defaultValue): ignores variable values that do not match the current field [`#5122`](https://github.com/nocobase/nocobase/pull/5122) +- chore(deps-dev): bump eslint-plugin-jest-dom from 5.1.0 to 5.4.0 [`#5138`](https://github.com/nocobase/nocobase/pull/5138) +- chore(deps): bump @ant-design/pro-layout from 7.17.16 to 7.19.12 [`#5137`](https://github.com/nocobase/nocobase/pull/5137) +- fix(template): fix error on form block submission [`#5133`](https://github.com/nocobase/nocobase/pull/5133) +- feat: add support for opening via URL [`#5098`](https://github.com/nocobase/nocobase/pull/5098) +- fix(release): decrypt token error occasionally [`#5143`](https://github.com/nocobase/nocobase/pull/5143) + +### Commits + +- chore(versions): 😊 publish v1.3.5-beta [`35e8f89`](https://github.com/nocobase/nocobase/commit/35e8f89c75800a612db27485c96196555f922273) +- Revert "chore(deps): bump @ant-design/pro-layout from 7.17.16 to 7.19.12 (#5137)" [`3f461ad`](https://github.com/nocobase/nocobase/commit/3f461ad8f079b4c2cf5975c1e26271f55021e08a) +- fix(release): pro image ci [`e45d450`](https://github.com/nocobase/nocobase/commit/e45d45015792138e7378741bdaf488de714b365d) + +## [v1.3.4-beta](https://github.com/nocobase/nocobase/compare/v1.3.3-beta...v1.3.4-beta) - 2024-08-27 + +### Merged + +- refactor: set remainsTheSame as the default value for field editing in bulk editing action [`#5124`](https://github.com/nocobase/nocobase/pull/5124) + +### Commits + +- chore(versions): 😊 publish v1.3.4-beta [`a011748`](https://github.com/nocobase/nocobase/commit/a0117480e037e48a23f59921110003047a1a174b) +- chore: update changelog [`3403e8d`](https://github.com/nocobase/nocobase/commit/3403e8d76684950d6962a6110a4440eb95856a35) + +## [v1.3.3-beta](https://github.com/nocobase/nocobase/compare/v1.3.2-beta...v1.3.3-beta) - 2024-08-27 + +### Merged + +- fix: use the built-in logo file [`#5032`](https://github.com/nocobase/nocobase/pull/5032) +- chore: optimize pro image build ci [`#5140`](https://github.com/nocobase/nocobase/pull/5140) + +### Commits + +- chore(versions): 😊 publish v1.3.3-beta [`9dffefb`](https://github.com/nocobase/nocobase/commit/9dffefb90a662789f9c4e12d2a088a73363c89db) +- chore: update changelog [`7c28f4d`](https://github.com/nocobase/nocobase/commit/7c28f4d06690d6b36701f773a933287c0a395a6d) +- fix(release): remove continue-on-error for build step [`5a41ab0`](https://github.com/nocobase/nocobase/commit/5a41ab063c8eea8bb0240cc6baf5d485b4fe9f84) + +## [v1.3.2-beta](https://github.com/nocobase/nocobase/compare/v1.3.1-beta...v1.3.2-beta) - 2024-08-26 + +### Commits + +- chore(versions): 😊 publish v1.3.2-beta [`dcadaa6`](https://github.com/nocobase/nocobase/commit/dcadaa666583b3fdc8e7caa6befd37ad442f56e6) +- chore(release): optimize release workflow [`6987d46`](https://github.com/nocobase/nocobase/commit/6987d46b3eb5d928f7fc3e1d3226578913b68820) +- chore: update changelog [`388b0e2`](https://github.com/nocobase/nocobase/commit/388b0e2a8869862c86cc365ae5f347b74a372e7e) + +## [v1.3.1-beta](https://github.com/nocobase/nocobase/compare/v1.3.0-beta...v1.3.1-beta) - 2024-08-26 + +### Merged + +- feat(publish): publish pro repos [`#5129`](https://github.com/nocobase/nocobase/pull/5129) +- fix(tree): missing collection schema [`#5131`](https://github.com/nocobase/nocobase/pull/5131) +- fix(cli): support upgrade to next [`#5130`](https://github.com/nocobase/nocobase/pull/5130) +- fix(client): fix field names of variable input [`#5128`](https://github.com/nocobase/nocobase/pull/5128) +- fix: cannot access 'ActionPage' before initialization [`#5125`](https://github.com/nocobase/nocobase/pull/5125) + +### Commits + +- chore(versions): 😊 publish v1.3.1-beta [`4aff92a`](https://github.com/nocobase/nocobase/commit/4aff92ad3bf338a8f798b3cc7460b32316f83d65) +- chore: update changelog [`4515f02`](https://github.com/nocobase/nocobase/commit/4515f0220f2b5854d5b3abbbdab8d116ba818669) +- fix: missing schema [`c4b8195`](https://github.com/nocobase/nocobase/commit/c4b819528a15f3f7294ce4027ea64342742881f3) ## [v1.3.0-beta](https://github.com/nocobase/nocobase/compare/v1.2.39-alpha...v1.3.0-beta) - 2024-08-25 diff --git a/CHANGELOG.zh-CN.md b/CHANGELOG.zh-CN.md new file mode 100644 index 0000000000..435f7bfba1 --- /dev/null +++ b/CHANGELOG.zh-CN.md @@ -0,0 +1,115 @@ +# 更新日志 + +本项目的所有重要更改都将记录在此文件中。 + +格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/), +并且本项目遵循 [语义化版本](https://semver.org/spec/v2.0.0.html)。 + +## [v1.3.19-beta](https://github.com/nocobase/nocobase/compare/v1.3.18-beta...v1.3.19-beta) - 2024-09-08 + +### 🐛 修复 + +- **[client]** 修复因弹窗与 Link 按钮一起使用,所导致的 URL 异常的问题 ([#5219](https://github.com/nocobase/nocobase/pull/5219)) by @zhangzhonghe + +## [v1.3.18-beta](https://github.com/nocobase/nocobase/compare/v1.3.17-beta...v1.3.18-beta) - 2024-09-08 + +### 🐛 修复 + +- **[数据表字段:多对多 (数组)]** 修复删除包含多对多(数组)字段的数据表时出现的错误 ([#5231](https://github.com/nocobase/nocobase/pull/5231)) by @2013xile + +## [v1.3.17-beta](https://github.com/nocobase/nocobase/compare/v1.3.16-beta...v1.3.17-beta) - 2024-09-07 + +### 🎉 新特性 + +- **[client]** 支持在子表单和子表格中配置联动规则。 ([#5159](https://github.com/nocobase/nocobase/pull/5159)) by @zhangzhonghe + +### 🚀 优化 + +- **[client]** + - 显示时间时默认时间为 00:00:00 ([#5226](https://github.com/nocobase/nocobase/pull/5226)) by @chenos + + - 插件依赖版本不一致时也可以激活插件 ([#5225](https://github.com/nocobase/nocobase/pull/5225)) by @chenos + +- **[server]** 提供更友好的应用级错误提示 ([#5220](https://github.com/nocobase/nocobase/pull/5220)) by @chenos + +### 🐛 修复 + +- **[client]** 修复在详情区块中出现的 “Maximum call stack size exceeded” 错误 ([#5228](https://github.com/nocobase/nocobase/pull/5228)) by @zhangzhonghe + +- **[数据表字段:多对多 (数组)]** 修复将 `uid` 类型的字段设置为多对多(数组)字段的目标键时出现的报错 ([#5229](https://github.com/nocobase/nocobase/pull/5229)) by @2013xile + +- **[UI schema 存储服务]** 修复 member 角色点击按钮报无权限的问题 ([#5206](https://github.com/nocobase/nocobase/pull/5206)) by @zhangzhonghe + +- **[工作流]** 修复创建工作流后类型列展示错误文字的问题 ([#5222](https://github.com/nocobase/nocobase/pull/5222)) by @mytharcher + +- **[用户]** 移除在用户管理中编辑用户资料时的手机号格式验证 ([#5221](https://github.com/nocobase/nocobase/pull/5221)) by @2013xile + +## [v1.3.16-beta](https://github.com/nocobase/nocobase/compare/v1.3.15-beta...v1.3.16-beta) - 2024-09-06 + +### 🚀 优化 + +- **[client]** + - 有UI配置权限但没有数据表查看权限时添加占位 ([#5208](https://github.com/nocobase/nocobase/pull/5208)) by @katherinehhh + + - 当缺少 logo 时,显示系统标题。 ([#5175](https://github.com/nocobase/nocobase/pull/5175)) by @maoyutofu + +- **[用户认证]** 系统标题支持换行 ([#5211](https://github.com/nocobase/nocobase/pull/5211)) by @chenos + +- **[工作流:SQL 节点]** 将 SQL 操作节点的结果数据结构调整为仅包含数据部分。 ([#5189](https://github.com/nocobase/nocobase/pull/5189)) by @mytharcher +Reference: [SQL 操作](https://docs-cn.nocobase.com/handbook/workflow/nodes/sql) +- **[权限控制]** 使 `Users & Permissions` 配置页的 `Permissions` Tab 面板可扩展。 ([#5216](https://github.com/nocobase/nocobase/pull/5216)) by @zhangzhonghe +Reference: [开发指南](https://docs-cn.nocobase.com/handbook/acl#%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97) +- **[操作:批量编辑]** 批量更新、批量编辑的 文案 ,“所有” 改成 “全表” ([#5200](https://github.com/nocobase/nocobase/pull/5200)) by @katherinehhh + +### 🐛 修复 + +- **[client]** + - 修复联动规则中切换赋值类型时组件显示错误 ([#5180](https://github.com/nocobase/nocobase/pull/5180)) by @katherinehhh + + - 修复数据范围中使用变量报错的问题。 ([#5195](https://github.com/nocobase/nocobase/pull/5195)) by @zhangzhonghe + + - 自定义请求按钮的请求后刷新数据设置不生效 ([#5188](https://github.com/nocobase/nocobase/pull/5188)) by @katherinehhh + +- **[数据可视化]** 修复聚合选项字段时,获取结果不正确的问题 ([#5214](https://github.com/nocobase/nocobase/pull/5214)) by @2013xile + +- **[数据源管理]** 修复`用户和权限`设置页面中数据源表格`rowKey`不正确问题 ([#5215](https://github.com/nocobase/nocobase/pull/5215)) by @gchust + +- **[工作流:HTTP 请求节点]** 修复请求节点参数使用非字符串变量时的问题。 ([#5204](https://github.com/nocobase/nocobase/pull/5204)) by @mytharcher + +- **[数据表字段:公式]** 修复公式字段时间类型测试用例 ([#5197](https://github.com/nocobase/nocobase/pull/5197)) by @katherinehhh + +- **[应用的备份与还原(废弃)]** 修复测试用例报错 ([#5201](https://github.com/nocobase/nocobase/pull/5201)) by @chenos + +- **[数据源:REST API]** + - rest api 数据表 标识不可编辑 by @katherinehhh + + - Rest api 多语言调整 by @katherinehhh + +## [v1.3.15-beta](https://github.com/nocobase/nocobase/compare/v1.3.14-beta...v1.3.15-beta) - 2024-09-04 + +### 🐛 修复 + +- **[工作流]** 修复工作流变量中缺少部分字段可选的问题。 ([#5187](https://github.com/nocobase/nocobase/pull/5187)) by @mytharcher + +- **[数据表字段:Markdown(Vditor)]** 修复 markdown(Vditor) 字段没有正确显数据(缓存) ([#5176](https://github.com/nocobase/nocobase/pull/5176)) by @katherinehhh + +## [v1.3.14-beta](https://github.com/nocobase/nocobase/compare/v1.3.13-beta...v1.3.14-beta) - 2024-09-03 + +### 🎉 新特性 + +- **[client]** 支持在筛选表单中配置对多关系目标表中的字段。 ([#5178](https://github.com/nocobase/nocobase/pull/5178)) by @zhangzhonghe + +### 🚀 优化 + +- **[操作:自定义请求]** 去掉添加数据表单自定义请求按钮的联动规则 ([#5179](https://github.com/nocobase/nocobase/pull/5179)) by @katherinehhh + +### 🐛 修复 + +- **[数据表字段:公式]** 公式字段使用日期字段时页面报错 ([#5168](https://github.com/nocobase/nocobase/pull/5168)) by @katherinehhh + +## [v1.3.13-beta](https://github.com/nocobase/nocobase/compare/v1.3.12-beta...v1.3.13-beta) - 2024-09-03 + +### 🐛 修复 + +- **[操作:导出记录]** 修复导出关系数据不正确的问题 ([#5170](https://github.com/nocobase/nocobase/pull/5170)) by @chareice + diff --git a/README.ja-JP.md b/README.ja-JP.md index ed16e72ee0..0e290fc8a3 100644 --- a/README.ja-JP.md +++ b/README.ja-JP.md @@ -8,18 +8,20 @@ https://github.com/user-attachments/assets/b11cbb68-76bc-4e8b-a2aa-2a1feed0ab77 NocoBase - Scalability-first, open-source no-code platform | Product Hunt ## 最近の重要なリリース -- [v1.0.1-alpha.1:ブロックの高さ設定をサポート - 2024/06/07](https://docs-cn.nocobase.com/welcome/changelog/20240607) -- [v1.0.0-alpha.15:新しいプラグインの追加、「設定操作」インターフェースの改善 - 2024/05/19](https://docs-cn.nocobase.com/welcome/changelog/20240519) -- [v1.0:新しいマイルストーン - 2024/04/28](https://docs-cn.nocobase.com/welcome/release/v1001-changelog) -- [v0.21:ブロックのパフォーマンスの最適化 - 2024/03/29](https://docs-cn.nocobase.com/welcome/release/v0210-changelog) -- [v0.20:複数のデータソースをサポート - 2024/03/03](https://docs-cn.nocobase.com/welcome/release/v0200-changelog) -- [v0.19:アプリケーションフローの最適化 - 2024/01/08](https://blog-cn.nocobase.com/posts/release-v019/) -- [v0.18:完全なテストシステムの確立 - 2023/12/21](https://blog-cn.nocobase.com/posts/release-v018/) -- [v0.17:新しいSchemaInitializerおよびSchemaSettings - 2023/12/11](https://blog-cn.nocobase.com/posts/release-v017/) -- [v0.16:新しいキャッシュモジュール - 2023/11/20](https://blog-cn.nocobase.com/posts/release-v016/) -- [v0.15:新しいプラグイン設定センター - 2023/11/13](https://blog-cn.nocobase.com/posts/release-v015/) -- [v0.14:新しいプラグインマネージャー、インターフェースを通じたプラグインの追加をサポート - 2023/09/11](https://blog-cn.nocobase.com/posts/release-v014/) -- [v0.13: 新しいアプリケーションステートフロー - 2023/08/24](https://blog-cn.nocobase.com/posts/release-v013/) + +- [v1.3:REST API データソース、モバイル版 V2 などの新機能 - 2024/08/29](https://www.nocobase.com/en/blog/nocobase-1-3) +- [v1.0.1-alpha.1:ブロックの高さ設定をサポート - 2024/06/07](https://www.nocobase.com/en/blog/release-v101-alpha1) +- [v1.0.0-alpha.15:新しいプラグインの追加、「設定操作」インターフェースの改善 - 2024/05/19](https://www.nocobase.com/en/blog/release-v100-alpha15) +- [v1.0:新しいマイルストーン - 2024/04/28](https://www.nocobase.com/en/blog/release-v10) +- [v0.21:ブロックのパフォーマンスの最適化 - 2024/03/29](https://www.nocobase.com/en/blog/release-v021) +- [v0.20:複数のデータソースをサポート - 2024/03/03](https://www.nocobase.com/en/blog/release-v020) +- [v0.19:アプリケーションフローの最適化 - 2024/01/08](https://www.nocobase.com/en/blog/release-v019) +- [v0.18:完全なテストシステムの確立 - 2023/12/21](https://www.nocobase.com/en/blog/release-v018) +- [v0.17:新しいSchemaInitializerおよびSchemaSettings - 2023/12/11](https://www.nocobase.com/en/blog/release-v017) +- [v0.16:新しいキャッシュモジュール - 2023/11/20](https://www.nocobase.com/en/blog/release-v016) +- [v0.15:新しいプラグイン設定センター - 2023/11/13](https://www.nocobase.com/en/blog/release-v015) +- [v0.14:新しいプラグインマネージャー、インターフェースを通じたプラグインの追加をサポート - 2023/09/11](https://www.nocobase.com/en/blog/release-v014) +- [v0.13: 新しいアプリケーションステートフロー - 2023/08/24](https://www.nocobase.com/en/blog/release-v013) ## NocoBaseはなに? diff --git a/README.md b/README.md index 58b7bbf81b..3937d5e6bb 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,19 @@ https://github.com/nocobase/nocobase/assets/1267426/1d6a3979-d1eb-4e50-b726-2f90 ## Recent major updates -- [v1.0.1-alpha.1: Blocks support height settings - 2024/06/07](https://docs.nocobase.com/welcome/changelog/20240607) -- [v1.0.0-alpha.15: New Plugins and Improved “Configure actions” Interaction - 2024/05/19](https://docs.nocobase.com/welcome/changelog/20240519) -- [v1.0: Significant Milestone - 2024/04/28](https://docs.nocobase.com/welcome/release/v1001-changelog) -- [v0.21: Block performance optimization - 2024/03/29](https://docs.nocobase.com/welcome/release/v0210-changelog) -- [v0.20: Support for multiple data sources - 2024/03/03](https://docs.nocobase.com/welcome/release/v0200-changelog) -- [v0.19: Application process optimization - 2024/01/08](https://docs.nocobase.com/welcome/release/v0190-changelog) -- [v0.18: Establish a sound testing system - 2023/12/21](https://docs.nocobase.com/welcome/release/v0180-changelog) -- [v0.17: New SchemaInitializer and SchemaSettings - 2023/12/11](https://docs.nocobase.com/welcome/release/v0170-changelog) -- [v0.16: New cache manager - 2023/11/20](https://docs.nocobase.com/welcome/release/v0160-changelog) -- [v0.15: New plugin settings manager - 2023/11/13](https://docs.nocobase.com/welcome/release/v0150-changelog) -- [v0.14: New plugin manager, supports adding plugins through UI - 2023/09/11](https://docs.nocobase.com/welcome/release/v0140-changelog) -- [v0.13: New application status flow - 2023/08/24](https://docs.nocobase.com/welcome/release/v0130-changelog) +- [v1.3: REST API data source, mobile v2, and more features - 2024/08/29](https://www.nocobase.com/en/blog/nocobase-1-3) +- [v1.0.1-alpha.1: Blocks support height settings - 2024/06/07](https://www.nocobase.com/en/blog/release-v101-alpha1) +- [v1.0.0-alpha.15: New Plugins and Improved “Configure actions” Interaction - 2024/05/19](https://www.nocobase.com/en/blog/release-v100-alpha15) +- [v1.0: Significant Milestone - 2024/04/28](https://www.nocobase.com/en/blog/release-v10) +- [v0.21: Block performance optimization - 2024/03/29](https://www.nocobase.com/en/blog/release-v021) +- [v0.20: Support for multiple data sources - 2024/03/03](https://www.nocobase.com/en/blog/release-v020) +- [v0.19: Application process optimization - 2024/01/08](https://www.nocobase.com/en/blog/release-v019) +- [v0.18: Establish a sound testing system - 2023/12/21](https://www.nocobase.com/en/blog/release-v018) +- [v0.17: New SchemaInitializer and SchemaSettings - 2023/12/11](https://www.nocobase.com/en/blog/release-v017) +- [v0.16: New cache manager - 2023/11/20](https://www.nocobase.com/en/blog/release-v016) +- [v0.15: New plugin settings manager - 2023/11/13](https://www.nocobase.com/en/blog/release-v015) +- [v0.14: New plugin manager, supports adding plugins through UI - 2023/09/11](https://www.nocobase.com/en/blog/release-v014) +- [v0.13: New application status flow - 2023/08/24](https://www.nocobase.com/en/blog/release-v013) ## What is NocoBase diff --git a/README.zh-CN.md b/README.zh-CN.md index 20dac826e7..b75e53ebc4 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -11,18 +11,19 @@ https://github.com/nocobase/nocobase/assets/1267426/29623e45-9a48-4598-bb9e-9dd1 我们正在招聘远程 **全栈开发工程师** 、 **测试工程师** 、 **技术培训与文档专家**。 欢迎对 NocoBase 有强烈兴趣的伙伴加入。[查看详情](https://www.nocobase.com/cn/recruitment) ## 最近重要更新 -- [v1.0.1-alpha.1:区块支持高度设置 - 2024/06/07](https://docs-cn.nocobase.com/welcome/changelog/20240607) -- [v1.0.0-alpha.15:新增插件、改进「配置操作」交互 - 2024/05/19](https://docs-cn.nocobase.com/welcome/changelog/20240519) -- [v1.0:新的里程碑 - 2024/04/28](https://docs-cn.nocobase.com/welcome/release/v1001-changelog) -- [v0.21:优化区块性能 - 2024/03/29](https://docs-cn.nocobase.com/welcome/release/v0210-changelog) -- [v0.20:支持多数据源 - 2024/03/03](https://docs-cn.nocobase.com/welcome/release/v0200-changelog) -- [v0.19:应用流程优化 - 2024/01/08](https://blog-cn.nocobase.com/posts/release-v019/) -- [v0.18:建立健全的测试体系 - 2023/12/21](https://blog-cn.nocobase.com/posts/release-v018/) -- [v0.17:全新的 SchemaInitializer 和 SchemaSettings - 2023/12/11](https://blog-cn.nocobase.com/posts/release-v017/) -- [v0.16:全新的缓存模块 - 2023/11/20](https://blog-cn.nocobase.com/posts/release-v016/) -- [v0.15:全新的插件设置中心 - 2023/11/13](https://blog-cn.nocobase.com/posts/release-v015/) -- [v0.14:全新的插件管理器,支持通过界面添加插件 - 2023/09/11](https://blog-cn.nocobase.com/posts/release-v014/) -- [v0.13: 全新的应用状态流转 - 2023/08/24](https://blog-cn.nocobase.com/posts/release-v013/) +- [v1.3:REST API 数据源、移动端 V2 和更多功能 - 2024/08/29](https://www.nocobase.com/cn/blog/nocobase-1-3) +- [v1.0.1-alpha.1:区块支持高度设置 - 2024/06/07](https://www.nocobase.com/cn/blog/release-v101-alpha1) +- [v1.0.0-alpha.15:新增插件、改进「配置操作」交互 - 2024/05/19](https://www.nocobase.com/cn/blog/release-v100-alpha15) +- [v1.0:新的里程碑 - 2024/04/28](https://www.nocobase.com/cn/blog/release-v10) +- [v0.21:优化区块性能 - 2024/03/29](https://www.nocobase.com/cn/blog/release-v021) +- [v0.20:支持多数据源 - 2024/03/03](https://www.nocobase.com/cn/blog/release-v020) +- [v0.19:应用流程优化 - 2024/01/08](https://www.nocobase.com/cn/blog/release-v019) +- [v0.18:建立健全的测试体系 - 2023/12/21](https://www.nocobase.com/cn/blog/release-v018) +- [v0.17:全新的 SchemaInitializer 和 SchemaSettings - 2023/12/11](https://www.nocobase.com/cn/blog/release-v017) +- [v0.16:全新的缓存模块 - 2023/11/20](https://www.nocobase.com/cn/blog/release-v016) +- [v0.15:全新的插件设置中心 - 2023/11/13](https://www.nocobase.com/cn/blog/release-v015) +- [v0.14:全新的插件管理器,支持通过界面添加插件 - 2023/09/11](https://www.nocobase.com/cn/blog/release-v014) +- [v0.13: 全新的应用状态流转 - 2023/08/24](https://www.nocobase.com/cn/blog/release-v013) ## NocoBase 是什么 diff --git a/lerna.json b/lerna.json index e3756b61e5..04e0f8d699 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.3.0-beta", + "version": "1.3.19-beta", "npmClient": "yarn", "useWorkspaces": true, "npmClientArgs": ["--ignore-engines"], diff --git a/packages/core/acl/package.json b/packages/core/acl/package.json index ce5d6db72e..6ab0a0adec 100644 --- a/packages/core/acl/package.json +++ b/packages/core/acl/package.json @@ -1,13 +1,13 @@ { "name": "@nocobase/acl", - "version": "1.3.0-beta", + "version": "1.3.19-beta", "description": "", "license": "AGPL-3.0", "main": "./lib/index.js", "types": "./lib/index.d.ts", "dependencies": { - "@nocobase/resourcer": "1.3.0-beta", - "@nocobase/utils": "1.3.0-beta", + "@nocobase/resourcer": "1.3.19-beta", + "@nocobase/utils": "1.3.19-beta", "minimatch": "^5.1.1" }, "repository": { diff --git a/packages/core/actions/package.json b/packages/core/actions/package.json index a3f405be51..cdbf83d1b2 100644 --- a/packages/core/actions/package.json +++ b/packages/core/actions/package.json @@ -1,14 +1,14 @@ { "name": "@nocobase/actions", - "version": "1.3.0-beta", + "version": "1.3.19-beta", "description": "", "license": "AGPL-3.0", "main": "./lib/index.js", "types": "./lib/index.d.ts", "dependencies": { - "@nocobase/cache": "1.3.0-beta", - "@nocobase/database": "1.3.0-beta", - "@nocobase/resourcer": "1.3.0-beta" + "@nocobase/cache": "1.3.19-beta", + "@nocobase/database": "1.3.19-beta", + "@nocobase/resourcer": "1.3.19-beta" }, "repository": { "type": "git", diff --git a/packages/core/app/client/public/nocobase.png b/packages/core/app/client/public/nocobase.png new file mode 100644 index 0000000000..6f6625caaa Binary files /dev/null and b/packages/core/app/client/public/nocobase.png differ diff --git a/packages/core/app/package.json b/packages/core/app/package.json index f6ddf9ada2..240d5a5396 100644 --- a/packages/core/app/package.json +++ b/packages/core/app/package.json @@ -1,17 +1,17 @@ { "name": "@nocobase/app", - "version": "1.3.0-beta", + "version": "1.3.19-beta", "description": "", "license": "AGPL-3.0", "main": "./lib/index.js", "types": "./lib/index.d.ts", "dependencies": { - "@nocobase/database": "1.3.0-beta", - "@nocobase/preset-nocobase": "1.3.0-beta", - "@nocobase/server": "1.3.0-beta" + "@nocobase/database": "1.3.19-beta", + "@nocobase/preset-nocobase": "1.3.19-beta", + "@nocobase/server": "1.3.19-beta" }, "devDependencies": { - "@nocobase/client": "1.3.0-beta" + "@nocobase/client": "1.3.19-beta" }, "repository": { "type": "git", diff --git a/packages/core/auth/package.json b/packages/core/auth/package.json index 8063b98093..cee18144ee 100644 --- a/packages/core/auth/package.json +++ b/packages/core/auth/package.json @@ -1,16 +1,16 @@ { "name": "@nocobase/auth", - "version": "1.3.0-beta", + "version": "1.3.19-beta", "description": "", "license": "AGPL-3.0", "main": "./lib/index.js", "types": "./lib/index.d.ts", "dependencies": { - "@nocobase/actions": "1.3.0-beta", - "@nocobase/cache": "1.3.0-beta", - "@nocobase/database": "1.3.0-beta", - "@nocobase/resourcer": "1.3.0-beta", - "@nocobase/utils": "1.3.0-beta", + "@nocobase/actions": "1.3.19-beta", + "@nocobase/cache": "1.3.19-beta", + "@nocobase/database": "1.3.19-beta", + "@nocobase/resourcer": "1.3.19-beta", + "@nocobase/utils": "1.3.19-beta", "@types/jsonwebtoken": "^8.5.8", "jsonwebtoken": "^8.5.1" }, diff --git a/packages/core/build/package.json b/packages/core/build/package.json index 9c6a762278..891c8cfe79 100644 --- a/packages/core/build/package.json +++ b/packages/core/build/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/build", - "version": "1.3.0-beta", + "version": "1.3.19-beta", "description": "Library build tool based on rollup.", "main": "lib/index.js", "types": "./lib/index.d.ts", diff --git a/packages/core/cache/package.json b/packages/core/cache/package.json index 2da0bc0c70..6d72217560 100644 --- a/packages/core/cache/package.json +++ b/packages/core/cache/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/cache", - "version": "1.3.0-beta", + "version": "1.3.19-beta", "description": "", "license": "AGPL-3.0", "main": "./lib/index.js", diff --git a/packages/core/cli/package.json b/packages/core/cli/package.json index 83a18015af..99e9c3b7f1 100644 --- a/packages/core/cli/package.json +++ b/packages/core/cli/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/cli", - "version": "1.3.0-beta", + "version": "1.3.19-beta", "description": "", "license": "AGPL-3.0", "main": "./src/index.js", @@ -8,7 +8,7 @@ "nocobase": "./bin/index.js" }, "dependencies": { - "@nocobase/app": "1.3.0-beta", + "@nocobase/app": "1.3.19-beta", "@types/fs-extra": "^11.0.1", "@umijs/utils": "3.5.20", "chalk": "^4.1.1", @@ -22,10 +22,10 @@ "portfinder": "^1.0.28", "serve": "^13.0.2", "tree-kill": "^1.2.2", - "tsx": "^4.6.2" + "tsx": "^4.19.0" }, "devDependencies": { - "@nocobase/devtools": "1.3.0-beta" + "@nocobase/devtools": "1.3.19-beta" }, "repository": { "type": "git", diff --git a/packages/core/cli/src/commands/upgrade.js b/packages/core/cli/src/commands/upgrade.js index 4dc501b846..a36c0ef1fe 100644 --- a/packages/core/cli/src/commands/upgrade.js +++ b/packages/core/cli/src/commands/upgrade.js @@ -23,6 +23,7 @@ module.exports = (cli) => { .command('upgrade') .allowUnknownOption() .option('--raw') + .option('--next') .option('-S|--skip-code-update') .action(async (options) => { if (hasTsNode()) promptForTs(); @@ -40,26 +41,32 @@ module.exports = (cli) => { await runAppCommand('upgrade'); return; } - // If ts-node is not installed, do not do the following - const appDevDir = resolve(process.cwd(), './storage/.app-dev'); - if (existsSync(appDevDir)) { - rmSync(appDevDir, { recursive: true, force: true }); - } + const rmAppDir = () => { + // If ts-node is not installed, do not do the following + const appDevDir = resolve(process.cwd(), './storage/.app-dev'); + if (existsSync(appDevDir)) { + rmSync(appDevDir, { recursive: true, force: true }); + } + }; const pkg = require('../../package.json'); // get latest version - const { stdout } = await run('npm', ['info', '@nocobase/cli', 'version'], { stdio: 'pipe' }); + const { stdout } = await run('npm', ['info', options.next ? '@nocobase/cli@next' : '@nocobase/cli', 'version'], { + stdio: 'pipe', + }); if (pkg.version === stdout) { await runAppCommand('upgrade'); + rmAppDir(); return; } const currentY = 1 * pkg.version.split('.')[1]; const latestY = 1 * stdout.split('.')[1]; - if (currentY > latestY) { + if (options.next || currentY > latestY) { await run('yarn', ['add', '@nocobase/cli@next', '@nocobase/devtools@next', '-W']); } else { await run('yarn', ['add', '@nocobase/cli', '@nocobase/devtools', '-W']); } await run('yarn', ['install']); await runAppCommand('upgrade'); + rmAppDir(); }); }; diff --git a/packages/core/cli/src/util.js b/packages/core/cli/src/util.js index c18ad659d0..eb3acd54f8 100644 --- a/packages/core/cli/src/util.js +++ b/packages/core/cli/src/util.js @@ -313,7 +313,7 @@ function areTimeZonesEqual(timeZone1, timeZone2) { } timeZone1 = getTimezonesByOffset(timeZone1); timeZone2 = getTimezonesByOffset(timeZone2); - return moment.tz(timeZone1).format() === moment.tz(timeZone2).format(); + return moment.tz(timeZone1).format('Z') === moment.tz(timeZone2).format('Z'); } exports.initEnv = function initEnv() { @@ -406,6 +406,10 @@ exports.initEnv = function initEnv() { process.env.DB_TIMEZONE = process.env.TZ; } + if (!/^[+-]\d{1,2}:\d{2}$/.test(process.env.DB_TIMEZONE)) { + process.env.DB_TIMEZONE = moment.tz(process.env.DB_TIMEZONE).format('Z'); + } + if (!areTimeZonesEqual(process.env.DB_TIMEZONE, process.env.TZ)) { throw new Error( `process.env.DB_TIMEZONE="${process.env.DB_TIMEZONE}" and process.env.TZ="${process.env.TZ}" are different`, diff --git a/packages/core/client/package.json b/packages/core/client/package.json index 3571fb3b52..4ede7232e5 100644 --- a/packages/core/client/package.json +++ b/packages/core/client/package.json @@ -1,6 +1,6 @@ { "name": "@nocobase/client", - "version": "1.3.0-beta", + "version": "1.3.19-beta", "license": "AGPL-3.0", "main": "lib/index.js", "module": "es/index.mjs", @@ -26,9 +26,9 @@ "@formily/reactive-react": "^2.2.27", "@formily/shared": "^2.2.27", "@formily/validator": "^2.2.27", - "@nocobase/evaluators": "1.3.0-beta", - "@nocobase/sdk": "1.3.0-beta", - "@nocobase/utils": "1.3.0-beta", + "@nocobase/evaluators": "1.3.19-beta", + "@nocobase/sdk": "1.3.19-beta", + "@nocobase/utils": "1.3.19-beta", "ahooks": "^3.7.2", "antd": "^5.12.8", "antd-style": "3.4.5", @@ -57,7 +57,7 @@ "react-i18next": "^11.15.1", "react-iframe": "~1.8.5", "react-image-lightbox": "^5.1.4", - "react-intersection-observer": "9.8.1", + "react-intersection-observer": "9.13.0", "react-js-cron": "^3.1.0", "react-quill": "^2.0.0", "react-router-dom": "^6.11.2", diff --git a/packages/core/client/src/acl/ACLProvider.tsx b/packages/core/client/src/acl/ACLProvider.tsx index e0b907a12f..0a8f685ca9 100644 --- a/packages/core/client/src/acl/ACLProvider.tsx +++ b/packages/core/client/src/acl/ACLProvider.tsx @@ -20,6 +20,7 @@ import { useResourceActionContext } from '../collection-manager/ResourceActionPr import { useDataSourceKey } from '../data-source/data-source/DataSourceProvider'; import { useRecord } from '../record-provider'; import { SchemaComponentOptions, useDesignable } from '../schema-component'; +import { CollectionNotAllowViewPlaceholder } from '../data-source'; import { useApp } from '../application'; @@ -204,6 +205,17 @@ export function useACLRoleContext() { }; } +/** + * Used to get whether the current user has permission to configure UI + * @returns {allowConfigUI: boolean} + */ +export function useUIConfigurationPermissions(): { allowConfigUI: boolean } { + const { allowAll, snippets } = useACLRoleContext(); + return { + allowConfigUI: allowAll || snippets.includes('ui.*'), + }; +} + export const ACLCollectionProvider = (props) => { const { allowAll, parseAction } = useACLRoleContext(); const app = useApp(); @@ -222,7 +234,7 @@ export const ACLCollectionProvider = (props) => { } const params = parseAction(actionPath, { schema }); if (!params) { - return null; + return ; } const [_, actionName] = actionPath.split(':'); params.actionName = actionName; diff --git a/packages/core/client/src/api-client/APIClient.ts b/packages/core/client/src/api-client/APIClient.ts index 5c6993aa3f..7e33287a08 100644 --- a/packages/core/client/src/api-client/APIClient.ts +++ b/packages/core/client/src/api-client/APIClient.ts @@ -92,7 +92,7 @@ export class APIClient extends APIClientSDK { return response; }, (error) => { - const errs = error?.response?.data?.errors || [{ message: 'Server error' }]; + const errs = this.toErrMessages(error); // Hard code here temporarily // TODO(yangqia): improve error code and message if (errs.find((error: { code?: string }) => error.code === 'ROLE_NOT_FOUND_ERR')) { @@ -103,6 +103,17 @@ export class APIClient extends APIClientSDK { ); } + toErrMessages(error) { + if (typeof error?.response?.data === 'string') { + return [{ message: error?.response?.data }]; + } + return ( + error?.response?.data?.errors || + error?.response?.data?.messages || + error?.response?.error || [{ message: error.message || 'Server error' }] + ); + } + useNotificationMiddleware() { this.axios.interceptors.response.use( (response) => { @@ -143,7 +154,7 @@ export class APIClient extends APIClientSDK { } else if (this.app.error) { this.app.error = null; } - let errs = error?.response?.data?.errors || error?.response?.data?.messages || [{ message: 'Server error' }]; + let errs = this.toErrMessages(error); errs = errs.filter((error) => { const lastTime = errorCache.get(error.message); if (lastTime && new Date().getTime() - lastTime < 500) { diff --git a/packages/core/client/src/application/Application.tsx b/packages/core/client/src/application/Application.tsx index 6b92e390f9..d190ee577a 100644 --- a/packages/core/client/src/application/Application.tsx +++ b/packages/core/client/src/application/Application.tsx @@ -34,17 +34,17 @@ import { compose, normalizeContainer } from './utils'; import { defineGlobalDeps } from './utils/globalDeps'; import { getRequireJs } from './utils/requirejs'; +import { CollectionFieldInterfaceComponentOption } from '../data-source/collection-field-interface/CollectionFieldInterface'; import { CollectionField } from '../data-source/collection-field/CollectionField'; import { DataSourceApplicationProvider } from '../data-source/components/DataSourceApplicationProvider'; import { DataBlockProvider } from '../data-source/data-block/DataBlockProvider'; import { DataSourceManager, type DataSourceManagerOptions } from '../data-source/data-source/DataSourceManager'; -import { CollectionFieldInterfaceComponentOption } from '../data-source/collection-field-interface/CollectionFieldInterface'; +import type { CollectionFieldInterfaceFactory } from '../data-source'; import { OpenModeProvider } from '../modules/popup/OpenModeProvider'; import { AppSchemaComponentProvider } from './AppSchemaComponentProvider'; import type { Plugin } from './Plugin'; import type { RequireJS } from './utils/requirejs'; -import type { CollectionFieldInterfaceFactory } from '../data-source'; declare global { interface Window { @@ -282,10 +282,21 @@ export class Application { }); } loadFailed = true; - const others = error?.response?.data?.error || error?.response?.data?.errors?.[0] || { message: error?.message }; + const toError = (error) => { + if (typeof error?.response?.data === 'string') { + return { message: error?.response?.data }; + } + if (error?.response?.data?.error) { + return error?.response?.data?.error; + } + if (error?.response?.data?.errors?.[0]) { + return error?.response?.data?.errors?.[0]; + } + return { message: error?.message }; + }; this.error = { code: 'LOAD_ERROR', - ...others, + ...toError(error), }; console.error(error, this.error); } diff --git a/packages/core/client/src/block-provider/DetailsBlockProvider.tsx b/packages/core/client/src/block-provider/DetailsBlockProvider.tsx index 42450701b0..cb050435cb 100644 --- a/packages/core/client/src/block-provider/DetailsBlockProvider.tsx +++ b/packages/core/client/src/block-provider/DetailsBlockProvider.tsx @@ -141,7 +141,8 @@ export const useDetailsBlockProps = () => { ctx.form .reset() .then(() => { - ctx.form.setValues(data || {}); + ctx.form.setInitialValues(data || {}); + // Using `ctx.form.setValues(data || {});` here may cause an internal infinite loop in Formily }) .catch(console.error); } diff --git a/packages/core/client/src/block-provider/FormBlockProvider.tsx b/packages/core/client/src/block-provider/FormBlockProvider.tsx index 6ec74f7ccb..d2abb74276 100644 --- a/packages/core/client/src/block-provider/FormBlockProvider.tsx +++ b/packages/core/client/src/block-provider/FormBlockProvider.tsx @@ -87,7 +87,7 @@ const InternalFormBlockProvider = (props) => { updateAssociationValues, ]); - if (service.loading && Object.keys(form?.initialValues)?.length === 0 && action) { + if (service.loading && Object.keys(form?.initialValues || {})?.length === 0 && action) { return ; } @@ -151,9 +151,10 @@ export const useFormBlockContext = () => { /** * @internal */ -export const useFormBlockProps = (shouldClearInitialValues = false) => { +export const useFormBlockProps = () => { const ctx = useFormBlockContext(); const treeParentRecord = useTreeParentRecord(); + useEffect(() => { if (treeParentRecord) { ctx.form?.query('parent').take((field) => { @@ -164,19 +165,14 @@ export const useFormBlockProps = (shouldClearInitialValues = false) => { }); useEffect(() => { - if (!ctx?.service?.loading) { - const form: Form = ctx.form; - if (form) { - // form 字段中可能一开始就存在一些默认值(比如设置了字段默认值的模板区块)。在编辑表单中, - // 这些默认值是不需要的,需要清除掉,不然会导致一些问题。比如:https://github.com/nocobase/nocobase/issues/4868 - if (shouldClearInitialValues === true) { - form.initialValues = {}; - form.reset(); - } - form.setInitialValues(ctx.service?.data?.data); - } + const form: Form = ctx.form; + + if (!form || ctx.service?.loading) { + return; } - }, [ctx?.service?.loading]); + + form.setInitialValues(ctx.service?.data?.data); + }, [ctx.form, ctx.service?.data?.data, ctx.service?.loading]); return { form: ctx.form, }; diff --git a/packages/core/client/src/block-provider/__tests__/hooks/index.test.ts b/packages/core/client/src/block-provider/__tests__/hooks/index.test.ts index 7500de347d..fda48baea6 100644 --- a/packages/core/client/src/block-provider/__tests__/hooks/index.test.ts +++ b/packages/core/client/src/block-provider/__tests__/hooks/index.test.ts @@ -24,7 +24,7 @@ describe('parseVariablesAndChangeParamsToQueryString', () => { { name: 'param3', value: 'value3' }, ]; const variables: any = { - parseVariable: vi.fn().mockResolvedValue('parsedValue'), + parseVariable: vi.fn().mockResolvedValue({ value: 'parsedValue' }), }; const localVariables: any = [ { name: '$var1', ctx: { value: 'localValue1' } }, diff --git a/packages/core/client/src/block-provider/hooks/index.ts b/packages/core/client/src/block-provider/hooks/index.ts index e66d10fcb0..25f1d9457a 100644 --- a/packages/core/client/src/block-provider/hooks/index.ts +++ b/packages/core/client/src/block-provider/hooks/index.ts @@ -28,6 +28,7 @@ import { useCollectionRecord, useDataSourceHeaders, useFormActiveFields, + useParsedFilter, useRouterBasename, useTableBlockContext, } from '../..'; @@ -41,7 +42,7 @@ import { useTreeParentRecord } from '../../modules/blocks/data-blocks/table/Tree import { useRecord } from '../../record-provider'; import { removeNullCondition, useActionContext, useCompile } from '../../schema-component'; import { isSubMode } from '../../schema-component/antd/association-field/util'; -import { replaceVariables } from '../../schema-component/antd/form-v2/utils'; +import { replaceVariables } from '../../schema-settings/LinkageRules/bindLinkageRulesToFiled'; import { useCurrentUserContext } from '../../user'; import { useLocalVariables, useVariables } from '../../variables'; import { VariableOption, VariablesContextType } from '../../variables/types'; @@ -163,9 +164,9 @@ export function useCollectValuesToSubmit() { } if (isVariable(value)) { - const result = await variables?.parseVariable(value, localVariables); - if (result) { - assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField }); + const { value: parsedValue } = (await variables?.parseVariable(value, localVariables)) || {}; + if (parsedValue) { + assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField }); } } else if (value != null && value !== '') { assignedValues[key] = value; @@ -324,9 +325,9 @@ export const useAssociationCreateActionProps = () => { } if (isVariable(value)) { - const result = await variables?.parseVariable(value, localVariables); - if (result) { - assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField }); + const { value: parsedValue } = (await variables?.parseVariable(value, localVariables)) || {}; + if (parsedValue) { + assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField }); } } else if (value != null && value !== '') { assignedValues[key] = value; @@ -582,9 +583,9 @@ export const useCustomizeUpdateActionProps = () => { } if (isVariable(value)) { - const result = await variables?.parseVariable(value, localVariables); - if (result) { - assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField }); + const { value: parsedValue } = (await variables?.parseVariable(value, localVariables)) || {}; + if (parsedValue) { + assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField }); } } else if (value != null && value !== '') { assignedValues[key] = value; @@ -680,9 +681,9 @@ export const useCustomizeBulkUpdateActionProps = () => { } if (isVariable(value)) { - const result = await variables?.parseVariable(value, localVariables); - if (result) { - assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField }); + const { value: parsedValue } = (await variables?.parseVariable(value, localVariables)) || {}; + if (parsedValue) { + assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField }); } } else if (value != null && value !== '') { assignedValues[key] = value; @@ -853,7 +854,7 @@ export const useUpdateActionProps = () => { const form = useForm(); const filterByTk = useFilterByTk(); const { field, resource, __parent } = useBlockRequestContext(); - const { setVisible, setSubmitted, setFormValueChanged } = useActionContext(); + const { setVisible, setFormValueChanged } = useActionContext(); const actionSchema = useFieldSchema(); const navigate = useNavigateNoUpdate(); const { fields, getField, name } = useCollection_deprecated(); @@ -867,7 +868,7 @@ export const useUpdateActionProps = () => { const { getActiveFieldsName } = useFormActiveFields() || {}; return { - async onClick() { + async onClick(e?, callBack?) { const { assignedValues: originalAssignedValues = {}, onSuccess, @@ -888,9 +889,9 @@ export const useUpdateActionProps = () => { } if (isVariable(value)) { - const result = await variables?.parseVariable(value, localVariables); - if (result) { - assignedValues[key] = transformVariableValue(result, { targetCollectionField: collectionField }); + const { value: parsedValue } = (await variables?.parseVariable(value, localVariables)) || {}; + if (parsedValue) { + assignedValues[key] = transformVariableValue(parsedValue, { targetCollectionField: collectionField }); } } else if (value != null && value !== '') { assignedValues[key] = value; @@ -930,8 +931,10 @@ export const useUpdateActionProps = () => { }); actionField.data.loading = false; // __parent?.service?.refresh?.(); + if (callBack) { + callBack?.(); + } setVisible?.(false); - setSubmitted?.(true); setFormValueChanged?.(false); if (!onSuccess?.successMessage) { return; @@ -1245,6 +1248,7 @@ export const useAssociationFilterBlockProps = () => { const { props: blockProps } = useBlockRequestContext(); const headers = useDataSourceHeaders(blockProps?.dataSource); const cm = useCollectionManager_deprecated(); + const { filter, parseVariableLoading } = useParsedFilter({ filterOption: field.componentProps?.params?.filter }); let list, handleSearchInput, params, run, data, valueKey, labelKey, filterKey; @@ -1264,6 +1268,7 @@ export const useAssociationFilterBlockProps = () => { pageSize: 200, page: 1, ...field.componentProps?.params, + filter, }, }, { @@ -1275,12 +1280,20 @@ export const useAssociationFilterBlockProps = () => { useEffect(() => { // 由于选项字段不需要触发当前请求,所以请求单独在关系字段的时候触发 - if (!isOptionalField(collectionField)) { + if (!isOptionalField(collectionField) && parseVariableLoading === false) { run(); } // do not format the dependencies - }, [collectionField, labelKey, run, valueKey, field.componentProps?.params, field.componentProps?.params?.sort]); + }, [ + collectionField, + labelKey, + run, + valueKey, + field.componentProps?.params, + field.componentProps?.params?.sort, + parseVariableLoading, + ]); if (!collectionField) { return {}; @@ -1661,8 +1674,8 @@ export async function parseVariablesAndChangeParamsToQueryString({ searchParams.map(async ({ name, value }) => { if (typeof value === 'string') { if (isVariable(value)) { - const result = await variables.parseVariable(value, localVariables); - return { name, value: result }; + const { value: parsedValue } = (await variables.parseVariable(value, localVariables)) || {}; + return { name, value: parsedValue }; } const result = await replaceVariableValue(value, variables, localVariables); return { name, value: result }; diff --git a/packages/core/client/src/data-source/components/CollectionDeletedPlaceholder.tsx b/packages/core/client/src/data-source/components/CollectionDeletedPlaceholder.tsx index fdf2aff562..024b938f38 100644 --- a/packages/core/client/src/data-source/components/CollectionDeletedPlaceholder.tsx +++ b/packages/core/client/src/data-source/components/CollectionDeletedPlaceholder.tsx @@ -16,6 +16,7 @@ import { useDataSourceManager } from '../data-source'; import { DEFAULT_DATA_SOURCE_KEY } from '../../data-source/data-source/DataSourceManager'; import { useCollection } from '../collection'; import { BlockItemCard } from '../../schema-component/antd/block-item/BlockItemCard'; +import { AnyKindOfDictionary } from 'lodash'; export interface CollectionDeletedPlaceholderProps { type: 'Collection' | 'Field' | 'Data Source' | 'Block template'; @@ -112,3 +113,51 @@ export const CollectionDeletedPlaceholder: FC return null; }; + +const CollectionNotAllowView = () => { + const { t } = useTranslation(); + const dataSource = useDataSource(); + const compile = useCompile(); + const collection = useCollection(); + const dataSourceManager = useDataSourceManager(); + const nameValue = useMemo(() => { + const dataSourcePrefix = + dataSourceManager?.getDataSources().length >= 1 && dataSource && dataSource.key !== DEFAULT_DATA_SOURCE_KEY + ? `${compile(dataSource.displayName || dataSource.key)} > ` + : ''; + if (collection) { + return `${dataSourcePrefix}${collection.name}`; + } + const collectionPrefix = collection + ? `${compile(collection.title) || collection.name || collection.tableName} > ` + : ''; + return `${dataSourcePrefix}${collectionPrefix}${collection.name}`; + }, []); + + const messageValue = useMemo(() => { + return t( + `The current user only has the UI configuration permission, but don't have view permission for collection "{{name}}"`, + { + name: nameValue, + }, + ).replaceAll('>', '>'); + }, [nameValue, t]); + return ( + + + + ); +}; + +/** + * @internal + */ +export const CollectionNotAllowViewPlaceholder: FC = () => { + const { designable } = useDesignable(); + + if (designable) { + return ; + } + + return null; +}; diff --git a/packages/core/client/src/data-source/data-block/DataBlockProvider.tsx b/packages/core/client/src/data-source/data-block/DataBlockProvider.tsx index 71964c2058..43ab9737a1 100644 --- a/packages/core/client/src/data-source/data-block/DataBlockProvider.tsx +++ b/packages/core/client/src/data-source/data-block/DataBlockProvider.tsx @@ -121,6 +121,27 @@ export interface DataBlockContextValue { export const DataBlockContext = createContext>({} as any); DataBlockContext.displayName = 'DataBlockContext'; +const DataBlockResourceContext = createContext<{ rerenderDataBlock: () => void }>(null); +const RerenderDataBlockProvider: FC = ({ children }) => { + const [hidden, setHidden] = React.useState(false); + const value = useMemo(() => { + return { + rerenderDataBlock: () => { + setHidden(true); + setTimeout(() => { + setHidden(false); + }); + }, + }; + }, []); + + if (hidden) { + return null; + } + + return {children}; +}; + /** * @internal */ @@ -170,7 +191,9 @@ export const DataBlockProvider: FC> = withDynamicSche - {children} + + {children} + @@ -195,3 +218,11 @@ export const useDataBlockProps = (): DataBlockContextValue['pro const context = useDataBlock(); return context.props; }; + +export const useRerenderDataBlock = () => { + const context = useContext(DataBlockResourceContext); + if (!context) { + throw new Error('useRerenderDataBlock() must be used within a DataBlockProvider'); + } + return context; +}; diff --git a/packages/core/client/src/locale/en_US.json b/packages/core/client/src/locale/en_US.json index cbfdf6fdb1..6ba6e86e55 100644 --- a/packages/core/client/src/locale/en_US.json +++ b/packages/core/client/src/locale/en_US.json @@ -838,5 +838,7 @@ "Open in new window": "Open in new window", "Sorry, the page you visited does not exist.": "Sorry, the page you visited does not exist.", "is none of": "is none of", - "is any of": "is any of" + "is any of": "is any of", + "Plugin dependency version mismatch": "Plugin dependency version mismatch", + "The current dependency version of the plugin does not match the version of the application and may not work properly. Are you sure you want to continue enabling the plugin?": "The current dependency version of the plugin does not match the version of the application and may not work properly. Are you sure you want to continue enabling the plugin?" } diff --git a/packages/core/client/src/locale/zh-CN.json b/packages/core/client/src/locale/zh-CN.json index 0227de524a..da4c448de8 100644 --- a/packages/core/client/src/locale/zh-CN.json +++ b/packages/core/client/src/locale/zh-CN.json @@ -971,5 +971,8 @@ "Use simple pagination mode": "使用简单分页模式", "Sorry, the page you visited does not exist.": "抱歉,你访问的页面不存在。", "Set Template Engine": "设置模板引擎", - "Skip getting the total number of table records during paging to speed up loading. It is recommended to enable this option for data tables with a large amount of data": "在分页时跳过获取表记录总数,以加快加载速度,建议对有大量数据的数据表开启此选项" + "Skip getting the total number of table records during paging to speed up loading. It is recommended to enable this option for data tables with a large amount of data": "在分页时跳过获取表记录总数,以加快加载速度,建议对有大量数据的数据表开启此选项", + "The current user only has the UI configuration permission, but don't have view permission for collection \"{{name}}\"": "当前用户只有 UI 配置权限,但没有数据表 \"{{name}}\" 查看权限。", + "Plugin dependency version mismatch": "插件依赖版本不一致", + "The current dependency version of the plugin does not match the version of the application and may not work properly. Are you sure you want to continue enabling the plugin?": "当前插件的依赖版本与应用的版本不一致,可能无法正常工作。您确定要继续激活插件吗?" } \ No newline at end of file diff --git a/packages/core/client/src/modules/blocks/data-blocks/details-single/ReadPrettyFormItemInitializers.tsx b/packages/core/client/src/modules/blocks/data-blocks/details-single/ReadPrettyFormItemInitializers.tsx index b47af5d54e..1386471f38 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/details-single/ReadPrettyFormItemInitializers.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/details-single/ReadPrettyFormItemInitializers.tsx @@ -85,6 +85,9 @@ const commonOptions = { type: 'void', 'x-editable': false, 'x-decorator': 'FormItem', + 'x-decorator-props': { + engine: 'handlebars', + }, // 'x-designer': 'Markdown.Void.Designer', 'x-toolbar': 'FormItemSchemaToolbar', 'x-settings': 'blockSettings:markdown', diff --git a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-edit/bulkEditForm.test.ts b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-edit/bulkEditForm.test.ts index ab3a456550..d4d5c94351 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-edit/bulkEditForm.test.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-edit/bulkEditForm.test.ts @@ -26,7 +26,10 @@ test.describe('bulk edit form', () => { // 2. 打开弹窗,显示出批量编辑表单 await page.getByLabel('action-Action-Bulk edit-').click(); - // 默认为 "Changed to" 模式,此时应该显示字段是必填的 + await page.getByLabel('block-item-BulkEditField-').click(); + await page.getByRole('option', { name: 'Changed to' }).click(); + + // "Changed to" 模式,此时应该显示字段是必填的 await expect(page.getByLabel('block-item-BulkEditField-').getByText('*')).toBeVisible(); // 3. 输入值,点击提交 @@ -45,7 +48,10 @@ test.describe('bulk edit form', () => { // 1. 打开弹窗,显示出批量编辑表单 await page.getByLabel('action-Action-Bulk edit-').click(); - // 默认为 "Changed to" 模式,此时应该显示字段是必填的 + await page.getByLabel('block-item-BulkEditField-').click(); + await page.getByRole('option', { name: 'Changed to' }).click(); + + // "Changed to" 模式,此时应该显示字段是必填的 await expect(page.getByLabel('block-item-BulkEditField-').getByText('*')).toBeVisible(); // 2. 切换为其它模式,此时应该不显示字段是必填的 diff --git a/packages/core/client/src/modules/blocks/data-blocks/form/fieldSettingsFormItem.tsx b/packages/core/client/src/modules/blocks/data-blocks/form/fieldSettingsFormItem.tsx index 3c8e65bfe2..522a0fcaf2 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/form/fieldSettingsFormItem.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/form/fieldSettingsFormItem.tsx @@ -6,29 +6,30 @@ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. * For more information, please refer to: https://www.nocobase.com/agreement. */ -import React from 'react'; import { ArrayCollapse, FormLayout } from '@formily/antd-v5'; import { Field } from '@formily/core'; import { ISchema, useField, useFieldSchema } from '@formily/react'; import _ from 'lodash'; +import React from 'react'; import { useTranslation } from 'react-i18next'; import { useApp, useSchemaToolbar } from '../../../../application'; import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings'; import { useFormBlockContext } from '../../../../block-provider/FormBlockProvider'; import { useCollectionManager_deprecated, useCollection_deprecated } from '../../../../collection-manager'; -import { useCollection } from '../../../../data-source'; import { useFieldComponentName } from '../../../../common/useFieldComponentName'; +import { useCollection } from '../../../../data-source'; +import { fieldComponentSettingsItem } from '../../../../data-source/commonsSettingsItem'; import { useDesignable, useValidateSchema } from '../../../../schema-component'; -import { useIsFormReadPretty } from '../../../../schema-component/antd/form-item/FormItem.Settings'; -import { getTempFieldState } from '../../../../schema-component/antd/form-v2/utils'; -import { isPatternDisabled } from '../../../../schema-settings'; +import { + useIsFieldReadPretty, + useIsFormReadPretty, +} from '../../../../schema-component/antd/form-item/FormItem.Settings'; +import { SchemaSettingsLinkageRules, isPatternDisabled } from '../../../../schema-settings'; +import { useIsAllowToSetDefaultValue } from '../../../../schema-settings/hooks/useIsAllowToSetDefaultValue'; +import { getTempFieldState } from '../../../../schema-settings/LinkageRules/bindLinkageRulesToFiled'; import { ActionType } from '../../../../schema-settings/LinkageRules/type'; import { SchemaSettingsDefaultValue } from '../../../../schema-settings/SchemaSettingsDefaultValue'; -import { useIsAllowToSetDefaultValue } from '../../../../schema-settings/hooks/useIsAllowToSetDefaultValue'; -import { fieldComponentSettingsItem } from '../../../../data-source/commonsSettingsItem'; -import { SchemaSettingsLinkageRules } from '../../../../schema-settings'; -import { useIsFieldReadPretty } from '../../../../schema-component/antd/form-item/FormItem.Settings'; export const fieldSettingsFormItem = new SchemaSettings({ name: 'fieldSettings:FormItem', items: [ diff --git a/packages/core/client/src/modules/blocks/data-blocks/form/hooks/useEditFormBlockProps.ts b/packages/core/client/src/modules/blocks/data-blocks/form/hooks/useEditFormBlockProps.ts index b5881055ec..b14581d866 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/form/hooks/useEditFormBlockProps.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/form/hooks/useEditFormBlockProps.ts @@ -10,5 +10,5 @@ import { useFormBlockProps } from '../../../../../block-provider/FormBlockProvider'; export function useEditFormBlockProps() { - return useFormBlockProps(true); + return useFormBlockProps(); } diff --git a/packages/core/client/src/modules/blocks/data-blocks/list/listBlockSettings.ts b/packages/core/client/src/modules/blocks/data-blocks/list/listBlockSettings.ts index 9a572040c3..8c0f8d5e63 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/list/listBlockSettings.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/list/listBlockSettings.ts @@ -187,11 +187,12 @@ export const listBlockSettings = new SchemaSettings({ title: t('Records per page'), value: field.decoratorProps?.params?.pageSize || 20, options: [ + { label: '5', value: 5 }, { label: '10', value: 10 }, { label: '20', value: 20 }, { label: '50', value: 50 }, - { label: '80', value: 80 }, { label: '100', value: 100 }, + { label: '200', value: 200 }, ], onChange: (pageSize) => { _.set(fieldSchema, 'x-decorator-props.params.pageSize', pageSize); diff --git a/packages/core/client/src/modules/blocks/data-blocks/table-selector/tableSelectorBlockSettings.ts b/packages/core/client/src/modules/blocks/data-blocks/table-selector/tableSelectorBlockSettings.ts index 1cf878c705..6be42ae99b 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/table-selector/tableSelectorBlockSettings.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/table-selector/tableSelectorBlockSettings.ts @@ -251,6 +251,7 @@ export const tableSelectorBlockSettings = new SchemaSettings({ title: t('Records per page'), value: field.decoratorProps?.params?.pageSize || 20, options: [ + { label: '5', value: 5 }, { label: '10', value: 10 }, { label: '20', value: 20 }, { label: '50', value: 50 }, diff --git a/packages/core/client/src/modules/blocks/data-blocks/table/tableBlockSettings.tsx b/packages/core/client/src/modules/blocks/data-blocks/table/tableBlockSettings.tsx index f7d70ff2b1..ec735aa1ad 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/table/tableBlockSettings.tsx +++ b/packages/core/client/src/modules/blocks/data-blocks/table/tableBlockSettings.tsx @@ -164,6 +164,7 @@ export const tableBlockSettings = new SchemaSettings({ title: t('Records per page'), value: field.decoratorProps?.params?.pageSize || 20, options: [ + { label: '5', value: 5 }, { label: '10', value: 10 }, { label: '20', value: 20 }, { label: '50', value: 50 }, diff --git a/packages/core/client/src/modules/blocks/filter-blocks/form/__e2e__/schemaInitializer.test.ts b/packages/core/client/src/modules/blocks/filter-blocks/form/__e2e__/schemaInitializer.test.ts index f1f8fde1ac..74e4ec18e4 100644 --- a/packages/core/client/src/modules/blocks/filter-blocks/form/__e2e__/schemaInitializer.test.ts +++ b/packages/core/client/src/modules/blocks/filter-blocks/form/__e2e__/schemaInitializer.test.ts @@ -8,7 +8,7 @@ */ import { Page, createBlockInPage, expect, oneEmptyFilterFormBlock, test } from '@nocobase/test/e2e'; -import { oneFilterFormWithInherit } from './templates'; +import { displayManyTOManyAssociationFields, oneFilterFormWithInherit } from './templates'; const deleteButton = async (page: Page, name: string) => { await page.getByLabel(`action-Action-${name}-`).hover(); @@ -82,6 +82,33 @@ test.describe('configure fields', () => { await expect(page.getByLabel('block-item-Markdown.Void-general-filter-form')).toBeVisible(); }); + test('display manyToMany association fields', async ({ page, mockPage, mockRecord }) => { + const nocoPage = await mockPage(displayManyTOManyAssociationFields).waitForInit(); + const record = await mockRecord('users', 2); + await nocoPage.goto(); + + // display the `users.roles` field + await page.getByLabel('schema-initializer-Grid-filterForm:configureFields-users').hover(); + await page.getByRole('menuitem', { name: 'Roles right' }).hover(); + await page.getByRole('menuitem', { name: 'Role name' }).click(); + + // before filter + await expect(page.getByRole('button', { name: record.roles.map((item) => item.name).join(',') })).toBeVisible(); + + // input a some value to filter + await page.getByLabel('block-item-CollectionField-').getByRole('textbox').fill('root'); + await page.getByLabel('action-Action-Filter-submit-').click({ + position: { + x: 10, + y: 10, + }, + }); + + // expect: should only display the record with the `root` role + await expect(page.getByRole('button', { name: 'root' })).toBeVisible(); + await expect(page.getByRole('button', { name: record.roles.map((item) => item.name).join(',') })).not.toBeVisible(); + }); + test.pgOnly('display inherit fields', async ({ page, mockPage }) => { await mockPage(oneFilterFormWithInherit).goto(); diff --git a/packages/core/client/src/modules/blocks/filter-blocks/form/__e2e__/templates.ts b/packages/core/client/src/modules/blocks/filter-blocks/form/__e2e__/templates.ts index ca5ce130b8..083b6fc132 100644 --- a/packages/core/client/src/modules/blocks/filter-blocks/form/__e2e__/templates.ts +++ b/packages/core/client/src/modules/blocks/filter-blocks/form/__e2e__/templates.ts @@ -1035,3 +1035,318 @@ export const T4798 = { 'x-index': 1, }, }; +export const displayManyTOManyAssociationFields = { + pageSchema: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Page', + properties: { + '9r802f25xpw': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'page:addBlock', + properties: { + y7zqna9ok64: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.13-beta', + properties: { + e01ptiv7169: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.13-beta', + properties: { + '8c03fmw7mv0': { + 'x-uid': 'osrsw28bg16', + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'FilterFormBlockProvider', + 'x-use-decorator-props': 'useFilterFormBlockDecoratorProps', + 'x-decorator-props': { + dataSource: 'main', + collection: 'users', + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:filterForm', + 'x-component': 'CardItem', + 'x-filter-targets': [ + { + uid: 'vl641x6hrd6', + }, + ], + 'x-app-version': '1.3.13-beta', + properties: { + vikjl7uaxai: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'FormV2', + 'x-use-component-props': 'useFilterFormBlockProps', + 'x-app-version': '1.3.13-beta', + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'filterForm:configureFields', + 'x-app-version': '1.3.13-beta', + 'x-uid': 'f38j0uj5vvq', + 'x-async': false, + 'x-index': 1, + }, + '0av2ioa3zjr': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'filterForm:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + layout: 'one-column', + style: { + float: 'right', + }, + }, + 'x-app-version': '1.3.13-beta', + properties: { + lsxm71r395m: { + _isJSONSchemaObject: true, + version: '2.0', + title: '{{ t("Filter") }}', + 'x-action': 'submit', + 'x-component': 'Action', + 'x-use-component-props': 'useFilterBlockActionProps', + 'x-designer': 'Action.Designer', + 'x-component-props': { + type: 'primary', + htmlType: 'submit', + }, + 'x-action-settings': {}, + type: 'void', + 'x-app-version': '1.3.13-beta', + 'x-uid': '6kj3gzqqubb', + 'x-async': false, + 'x-index': 1, + }, + '7ukzfx8trmp': { + _isJSONSchemaObject: true, + version: '2.0', + title: '{{ t("Reset") }}', + 'x-component': 'Action', + 'x-use-component-props': 'useResetBlockActionProps', + 'x-designer': 'Action.Designer', + 'x-action-settings': {}, + type: 'void', + 'x-app-version': '1.3.13-beta', + 'x-uid': '48rt2qm0rip', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'rv5qfqgaxs1', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'pmc05hvyobp', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'bzkbofpet3m', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'wlpzuhipi0n', + 'x-async': false, + 'x-index': 1, + }, + ektxc4qt6td: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-app-version': '1.3.13-beta', + properties: { + zftl6dfyeia: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-app-version': '1.3.13-beta', + properties: { + broo6eq0m2p: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableBlockProvider', + 'x-acl-action': 'users:list', + 'x-use-decorator-props': 'useTableBlockDecoratorProps', + 'x-decorator-props': { + collection: 'users', + dataSource: 'main', + action: 'list', + params: { + pageSize: 20, + }, + rowKey: 'id', + showIndex: true, + dragSort: false, + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:table', + 'x-component': 'CardItem', + 'x-filter-targets': [], + 'x-app-version': '1.3.13-beta', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'table:configureActions', + 'x-component': 'ActionBar', + 'x-component-props': { + style: { + marginBottom: 'var(--nb-spacing)', + }, + }, + 'x-app-version': '1.3.13-beta', + 'x-uid': 'w46bhgi0e9b', + 'x-async': false, + 'x-index': 1, + }, + c4fknjf3dgj: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'array', + 'x-initializer': 'table:configureColumns', + 'x-component': 'TableV2', + 'x-use-component-props': 'useTableBlockProps', + 'x-component-props': { + rowKey: 'id', + rowSelection: { + type: 'checkbox', + }, + }, + 'x-app-version': '1.3.13-beta', + properties: { + actions: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + title: '{{ t("Actions") }}', + 'x-action-column': 'actions', + 'x-decorator': 'TableV2.Column.ActionBar', + 'x-component': 'TableV2.Column', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-initializer': 'table:configureItemActions', + 'x-settings': 'fieldSettings:TableColumn', + 'x-toolbar-props': { + initializer: 'table:configureItemActions', + }, + 'x-app-version': '1.3.13-beta', + properties: { + '5mb0ksk9n7d': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'DndContext', + 'x-component': 'Space', + 'x-component-props': { + split: '|', + }, + 'x-app-version': '1.3.13-beta', + 'x-uid': 'b8lt0cpa8sk', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'uygsokuvjjo', + 'x-async': false, + 'x-index': 1, + }, + blco479x6xr: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-decorator': 'TableV2.Column.Decorator', + 'x-toolbar': 'TableColumnSchemaToolbar', + 'x-settings': 'fieldSettings:TableColumn', + 'x-component': 'TableV2.Column', + 'x-app-version': '1.3.13-beta', + properties: { + roles: { + _isJSONSchemaObject: true, + version: '2.0', + 'x-collection-field': 'users.roles', + 'x-component': 'CollectionField', + 'x-component-props': { + fieldNames: { + value: 'name', + label: 'name', + }, + ellipsis: true, + size: 'small', + }, + 'x-read-pretty': true, + 'x-decorator': null, + 'x-decorator-props': { + labelStyle: { + display: 'none', + }, + }, + 'x-app-version': '1.3.13-beta', + 'x-uid': 'gocdkziqwlz', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '3pfwlp96981', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'c6nm8b9emvw', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'vl641x6hrd6', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '5x1f6kzfwce', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'o9e7bi2jvp7', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'bcez1hvmvm0', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '1z5t01zmibi', + 'x-async': true, + 'x-index': 1, + }, + keepUid: true, +}; diff --git a/packages/core/client/src/modules/blocks/other-blocks/markdown/MarkdownFormItemInitializer.tsx b/packages/core/client/src/modules/blocks/other-blocks/markdown/MarkdownFormItemInitializer.tsx index 18f2be306f..3766d7e2db 100644 --- a/packages/core/client/src/modules/blocks/other-blocks/markdown/MarkdownFormItemInitializer.tsx +++ b/packages/core/client/src/modules/blocks/other-blocks/markdown/MarkdownFormItemInitializer.tsx @@ -23,6 +23,9 @@ export const MarkdownFormItemInitializer = () => { type: 'void', 'x-editable': false, 'x-decorator': 'FormItem', + 'x-decorator-props': { + engine: 'handlebars', + }, // 'x-designer': 'Markdown.Void.Designer', 'x-toolbar': 'FormItemSchemaToolbar', 'x-settings': 'blockSettings:markdown', diff --git a/packages/core/client/src/modules/fields/component/Nester/subformComponentFieldSettings.tsx b/packages/core/client/src/modules/fields/component/Nester/subformComponentFieldSettings.tsx index 35de555712..8540f64156 100644 --- a/packages/core/client/src/modules/fields/component/Nester/subformComponentFieldSettings.tsx +++ b/packages/core/client/src/modules/fields/component/Nester/subformComponentFieldSettings.tsx @@ -20,7 +20,7 @@ import { useIsFieldReadPretty, useIsFormReadPretty, } from '../../../../schema-component/antd/form-item/FormItem.Settings'; -import { setDefaultSortingRules } from '../SubTable/subTablePopoverComponentFieldSettings'; +import { linkageRules, setDefaultSortingRules } from '../SubTable/subTablePopoverComponentFieldSettings'; const allowMultiple: any = { name: 'allowMultiple', @@ -142,5 +142,6 @@ export const subformComponentFieldSettings = new SchemaSettings({ }, }, setDefaultSortingRules, + linkageRules, ], }); diff --git a/packages/core/client/src/modules/fields/component/Picker/TableSelectorInitializers.tsx b/packages/core/client/src/modules/fields/component/Picker/TableSelectorInitializers.tsx index d8ba0d3525..d07cb8914f 100644 --- a/packages/core/client/src/modules/fields/component/Picker/TableSelectorInitializers.tsx +++ b/packages/core/client/src/modules/fields/component/Picker/TableSelectorInitializers.tsx @@ -70,21 +70,9 @@ const commonOptions = { name: 'otherBlocks', children: [ { - title: '{{t("Add text")}}', - Component: 'BlockItemInitializer', - name: 'addText', - schema: { - type: 'void', - 'x-editable': false, - 'x-decorator': 'BlockItem', - // 'x-designer': 'Markdown.Void.Designer', - 'x-toolbar': 'BlockSchemaToolbar', - 'x-settings': 'blockSettings:markdown', - 'x-component': 'Markdown.Void', - 'x-component-props': { - content: '{{t("This is a demo text, **supports Markdown syntax**.")}}', - }, - }, + name: 'markdown', + title: '{{t("Markdown")}}', + Component: 'MarkdownBlockInitializer', }, ], }, diff --git a/packages/core/client/src/modules/fields/component/PopoverNester/subformPopoverComponentFieldSettings.tsx b/packages/core/client/src/modules/fields/component/PopoverNester/subformPopoverComponentFieldSettings.tsx index 0149ddcede..d7c7e616ef 100644 --- a/packages/core/client/src/modules/fields/component/PopoverNester/subformPopoverComponentFieldSettings.tsx +++ b/packages/core/client/src/modules/fields/component/PopoverNester/subformPopoverComponentFieldSettings.tsx @@ -12,12 +12,13 @@ import { useField, useFieldSchema } from '@formily/react'; import { useTranslation } from 'react-i18next'; import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings'; import { useFieldComponentName } from '../../../../common/useFieldComponentName'; +import { useCollectionField } from '../../../../data-source'; import { useDesignable, useFieldModeOptions, useIsAddNewForm } from '../../../../schema-component'; import { isSubMode } from '../../../../schema-component/antd/association-field/util'; import { useIsFieldReadPretty } from '../../../../schema-component/antd/form-item/FormItem.Settings'; -import { useCollectionField } from '../../../../data-source'; import { useColumnSchema } from '../../../../schema-component/antd/table-v2/Table.Column.Decorator'; import { titleField } from '../Picker/recordPickerComponentFieldSettings'; +import { linkageRules } from '../SubTable/subTablePopoverComponentFieldSettings'; const allowMultiple: any = { name: 'allowMultiple', @@ -103,5 +104,5 @@ const fieldComponent: any = { export const subformPopoverComponentFieldSettings = new SchemaSettings({ name: 'fieldSettings:component:PopoverNester', - items: [fieldComponent, allowMultiple, titleField], + items: [fieldComponent, allowMultiple, titleField, linkageRules], }); diff --git a/packages/core/client/src/modules/fields/component/SubTable/subTablePopoverComponentFieldSettings.tsx b/packages/core/client/src/modules/fields/component/SubTable/subTablePopoverComponentFieldSettings.tsx index a0d9d1aada..06339a4bd5 100644 --- a/packages/core/client/src/modules/fields/component/SubTable/subTablePopoverComponentFieldSettings.tsx +++ b/packages/core/client/src/modules/fields/component/SubTable/subTablePopoverComponentFieldSettings.tsx @@ -7,12 +7,17 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ +import { ArrayItems } from '@formily/antd-v5'; import { Field } from '@formily/core'; -import { useField, useFieldSchema, ISchema } from '@formily/react'; +import { ISchema, useField, useFieldSchema } from '@formily/react'; +import React from 'react'; import { useTranslation } from 'react-i18next'; -import { ArrayItems } from '@formily/antd-v5'; import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings'; +import { useCollectionManager_deprecated, useSortFields } from '../../../../collection-manager'; import { useFieldComponentName } from '../../../../common/useFieldComponentName'; +import { useCollectionManager, useRerenderDataBlock } from '../../../../data-source'; +import { FlagProvider } from '../../../../flag-provider/FlagProvider'; +import { withDynamicSchemaProps } from '../../../../hoc/withDynamicSchemaProps'; import { useDesignable, useFieldModeOptions, @@ -20,8 +25,9 @@ import { useIsFieldReadPretty, } from '../../../../schema-component'; import { isSubMode } from '../../../../schema-component/antd/association-field/util'; -import { useCollectionManager_deprecated, useSortFields } from '../../../../collection-manager'; import { useIsAssociationField } from '../../../../schema-component/antd/form-item'; +import { FormLinkageRules } from '../../../../schema-settings/LinkageRules'; +import { SchemaSettingsLinkageRules } from '../../../../schema-settings/SchemaSettings'; const fieldComponent: any = { name: 'fieldComponent', @@ -253,7 +259,39 @@ export const allowAddNewData = { }; }, }; + +const LinkageRulesComponent = withDynamicSchemaProps( + (props) => { + return ( + // the purpose is to display the `Current object` variable in the linkage rule configuration dialog + + + + ); + }, + { displayName: 'LinkageRulesComponent' }, +); + +export const linkageRules = { + name: 'linkageRules', + Component: SchemaSettingsLinkageRules, + useComponentProps() { + const field = useField(); + const fieldSchema = useFieldSchema(); + const cm = useCollectionManager(); + const collectionField = cm.getCollectionField(fieldSchema['x-collection-field']); + const { rerenderDataBlock } = useRerenderDataBlock(); + + return { + collectionName: collectionField?.target, + Component: LinkageRulesComponent, + readPretty: field.readPretty, + afterSubmit: rerenderDataBlock, + }; + }, +}; + export const subTablePopoverComponentFieldSettings = new SchemaSettings({ name: 'fieldSettings:component:SubTable', - items: [fieldComponent, allowAddNewData, allowSelectExistingRecord, setDefaultSortingRules], + items: [fieldComponent, allowAddNewData, allowSelectExistingRecord, setDefaultSortingRules, linkageRules], }); diff --git a/packages/core/client/src/modules/popup/PopupContextProvider.tsx b/packages/core/client/src/modules/popup/PopupContextProvider.tsx index 4aead69568..488aa5abdc 100644 --- a/packages/core/client/src/modules/popup/PopupContextProvider.tsx +++ b/packages/core/client/src/modules/popup/PopupContextProvider.tsx @@ -17,7 +17,10 @@ import { PopupVisibleProvider, PopupVisibleProviderContext } from '../../schema- * @param props * @returns */ -export const PopupContextProvider: React.FC = (props) => { +export const PopupContextProvider: React.FC<{ + visible?: boolean; + setVisible?: (visible: boolean) => void; +}> = (props) => { const [visible, setVisible] = useState(false); const { visible: visibleWithURL, setVisible: setVisibleWithURL } = useContext(PopupVisibleProviderContext) || { visible: false, @@ -26,10 +29,11 @@ export const PopupContextProvider: React.FC = (props) => { const fieldSchema = useFieldSchema(); const _setVisible = useCallback( (value: boolean): void => { + props.setVisible?.(value); setVisible?.(value); setVisibleWithURL?.(value); }, - [setVisibleWithURL], + [props, setVisibleWithURL], ); const openMode = fieldSchema['x-component-props']?.['openMode'] || 'drawer'; const openSize = fieldSchema['x-component-props']?.['openSize']; @@ -37,7 +41,7 @@ export const PopupContextProvider: React.FC = (props) => { return ( { await addBlock('form Table'); await addBlock('form Form'); await addBlock('Collapse'); - await addBlock('Add text'); + await addBlock('Markdown'); await expect(page.getByLabel('block-item-CardItem-roles-table-selector')).toBeVisible(); await expect(page.getByLabel('block-item-CardItem-roles-filter-form')).toBeVisible(); await expect(page.getByLabel('block-item-CardItem-roles-filter-collapse')).toBeVisible(); - await expect(page.getByLabel('block-item-Markdown.Void-roles-form')).toBeVisible(); + await expect(page.getByLabel('block-item-Markdown.Void-')).toBeVisible(); async function addBlock(name: string) { await page.getByLabel('schema-initializer-Grid-popup:tableSelector:addBlock-roles').hover(); diff --git a/packages/core/client/src/modules/popup/__e2e__/subPage.test.ts b/packages/core/client/src/modules/popup/__e2e__/subPage.test.ts index 2ecf6fde46..4d48df81f3 100644 --- a/packages/core/client/src/modules/popup/__e2e__/subPage.test.ts +++ b/packages/core/client/src/modules/popup/__e2e__/subPage.test.ts @@ -40,7 +40,7 @@ test.describe('sub page', () => { page.getByLabel('block-item-Markdown.Void-').getByText('Markdown:从弹窗中打开的子页面,tab2。'), ).toBeVisible(); await page.getByLabel('back-button').click(); - await page.goBack(); + await page.getByLabel('drawer-Action.Container-users-View record-mask').click(); // 从嵌套弹窗中打开子页面 -------------------------------------------------------------------- await page.getByLabel('action-Action.Link-View-view-').nth(2).click(); diff --git a/packages/core/client/src/nocobase-buildin-plugin/index.tsx b/packages/core/client/src/nocobase-buildin-plugin/index.tsx index e846be160a..a27b741482 100644 --- a/packages/core/client/src/nocobase-buildin-plugin/index.tsx +++ b/packages/core/client/src/nocobase-buildin-plugin/index.tsx @@ -142,7 +142,7 @@ const getProps = (app: Application) => { }; } - if (app.error.code === 'APP_ERROR' || app.error.code === 'LOAD_ERROR') { + if (['ENOENT', 'APP_ERROR', 'LOAD_ERROR'].includes(app.error.code)) { return { status: 'error', title: 'App error', @@ -205,7 +205,11 @@ const getProps = (app: Application) => { return { ...props, ...commands[app.error?.command?.name] }; } - return {}; + return { + status: 'warning', + title: 'App warning', + subTitle: app.error?.message, + }; }; const AppMaintaining: FC<{ app: Application; error: Error }> = observer( diff --git a/packages/core/client/src/pm/PluginCard.tsx b/packages/core/client/src/pm/PluginCard.tsx index 4ee053ac05..45b0c42db0 100644 --- a/packages/core/client/src/pm/PluginCard.tsx +++ b/packages/core/client/src/pm/PluginCard.tsx @@ -7,7 +7,7 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import { App, Card, Divider, Popconfirm, Space, Switch, Typography, message } from 'antd'; +import { App, Card, Divider, Popconfirm, Space, Switch, Typography } from 'antd'; import classnames from 'classnames'; import React, { FC, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -157,7 +157,20 @@ function PluginInfo(props: IPluginInfo) { onChange={async (checked, e) => { e.stopPropagation(); if (!isCompatible && checked) { - message.error(t("Dependencies check failed, can't enable.")); + modal.confirm({ + title: t('Plugin dependency version mismatch'), + content: t( + 'The current dependency version of the plugin does not match the version of the application and may not work properly. Are you sure you want to continue enabling the plugin?', + ), + onOk: async () => { + await api.request({ + url: `pm:enable`, + params: { + filterByTk: name, + }, + }); + }, + }); return; } if (!checked) { diff --git a/packages/core/client/src/route-switch/antd/admin-layout/index.tsx b/packages/core/client/src/route-switch/antd/admin-layout/index.tsx index a81d461af6..2557af79b6 100644 --- a/packages/core/client/src/route-switch/antd/admin-layout/index.tsx +++ b/packages/core/client/src/route-switch/antd/admin-layout/index.tsx @@ -429,15 +429,33 @@ export const InternalAdminLayout = () => { align-items: center; `} > - + {result?.data?.data?.logo?.url ? ( + + ) : ( + + {result?.data?.data?.title} + + )}
- workflowPlugin - .getTriggersOptions() + triggerOptions .filter((item) => { return typeof item.options.isActionTriggerable === 'function' || item.options.isActionTriggerable === true; }) .map((item) => item.value), - [workflowPlugin], + [triggerOptions], ); useFormEffects(() => { @@ -435,7 +435,7 @@ function WorkflowSelect({ formAction, buttonAction, actionType, ...props }) { }} optionFilter={optionFilter} optionRender={({ label, data }) => { - const typeOption = workflowPlugin.getTriggersOptions().find((item) => item.value === data.type); + const typeOption = triggerOptions.find((item) => item.value === data.type); return typeOption ? ( {label} diff --git a/packages/core/client/src/schema-component/antd/action/Action.Page.tsx b/packages/core/client/src/schema-component/antd/action/Action.Page.tsx index e06898c3e6..885d1e93e6 100644 --- a/packages/core/client/src/schema-component/antd/action/Action.Page.tsx +++ b/packages/core/client/src/schema-component/antd/action/Action.Page.tsx @@ -15,45 +15,41 @@ import { BackButtonUsedInSubPage } from '../page/BackButtonUsedInSubPage'; import { TabsContextProvider, useTabsContext } from '../tabs/context'; import { useActionPageStyle } from './Action.Page.style'; import { usePopupOrSubpagesContainerDOM } from './hooks/usePopupSlotDOM'; -import { ComposedActionDrawer } from './types'; -export const ActionPage: ComposedActionDrawer = observer( - ({ level }) => { - const filedSchema = useFieldSchema(); - const ctx = useActionContext(); - const { getContainerDOM } = usePopupOrSubpagesContainerDOM(); - const { styles } = useActionPageStyle(); - const tabContext = useTabsContext(); - - const style = useMemo(() => { - return { - // 20 is the z-index value of the main page - zIndex: 20 + level, - }; - }, [level]); - - if (!ctx.visible) { - return null; - } +export function ActionPage({ level }) { + const filedSchema = useFieldSchema(); + const ctx = useActionContext(); + const { getContainerDOM } = usePopupOrSubpagesContainerDOM(); + const { styles } = useActionPageStyle(); + const tabContext = useTabsContext(); + + const style = useMemo(() => { + return { + // 20 is the z-index value of the main page + zIndex: 20 + level, + }; + }, [level]); + + if (!ctx.visible) { + return null; + } - const actionPageNode = ( -
- }> - - -
- ); + const actionPageNode = ( +
+ }> + + +
+ ); - const container = getContainerDOM(); + const container = getContainerDOM(); - if (container) { - return createPortal(actionPageNode, container); - } + if (container) { + return createPortal(actionPageNode, container); + } - return actionPageNode; - }, - { displayName: 'ActionPage' }, -); + return actionPageNode; +} ActionPage.Footer = observer( () => { diff --git a/packages/core/client/src/schema-component/antd/action/Action.tsx b/packages/core/client/src/schema-component/antd/action/Action.tsx index 773c996623..32676ec10a 100644 --- a/packages/core/client/src/schema-component/antd/action/Action.tsx +++ b/packages/core/client/src/schema-component/antd/action/Action.tsx @@ -172,6 +172,7 @@ export const Action: ComposedAction = withDynamicSchemaProps( run, confirm, modal, + setSubmitted: setParentSubmitted, }; const buttonElement = RenderButton(buttonProps); @@ -304,6 +305,7 @@ function RenderButton({ run, confirm, modal, + setSubmitted, }) { const { t } = useTranslation(); const { isPopupVisibleControlledByURL } = usePopupSettings(); @@ -322,6 +324,7 @@ function RenderButton({ if (onClick) { onClick(e, () => { if (refreshDataBlockRequest !== false) { + setSubmitted?.(true); service?.refresh?.(); } }); diff --git a/packages/core/client/src/schema-component/antd/association-field/AssociationSelect.tsx b/packages/core/client/src/schema-component/antd/association-field/AssociationSelect.tsx index dee73be25d..41e14b6063 100644 --- a/packages/core/client/src/schema-component/antd/association-field/AssociationSelect.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/AssociationSelect.tsx @@ -41,7 +41,7 @@ export const filterAnalyses = (filters): any[] => { if (!operator) { return true; } - const regex = /\{\{\$(?:[a-zA-Z_]\w*)\.([a-zA-Z_]\w*)(?:\.id)?\}\}/; + const regex = /\{\{\$[a-zA-Z_]\w*(?:\.[a-zA-Z_]\w*)*\.(\w+)\.id\}\}/; const fieldName = jsonlogic?.value?.match?.(regex)?.[1]; if (fieldName) { results.push(fieldName); @@ -79,9 +79,13 @@ const InternalAssociationSelect = observer( //支持深层次子表单 onFieldInputValueChange('*', (fieldPath: any) => { const linkageFields = filterAnalyses(field.componentProps?.service?.params?.filter) || []; - if (linkageFields.includes(fieldPath?.props?.name) && field.value) { - field.setValue(field.initialValue); - setInnerValue(field.initialValue); + if ( + linkageFields.includes(fieldPath?.props?.name) && + field.value && + fieldPath?.indexes?.[0] === field?.indexes?.[0] + ) { + field.setValue(undefined); + setInnerValue(undefined); } }); }); diff --git a/packages/core/client/src/schema-component/antd/association-field/Nester.tsx b/packages/core/client/src/schema-component/antd/association-field/Nester.tsx index e05880a826..3d219697e9 100644 --- a/packages/core/client/src/schema-component/antd/association-field/Nester.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/Nester.tsx @@ -10,12 +10,12 @@ import { CloseOutlined, PlusOutlined } from '@ant-design/icons'; import { css } from '@emotion/css'; import { ArrayField } from '@formily/core'; +import { spliceArrayState } from '@formily/core/esm/shared/internals'; import { RecursionField, observer, useFieldSchema } from '@formily/react'; import { action } from '@formily/reactive'; import { each } from '@formily/shared'; import { Button, Card, Divider, Tooltip } from 'antd'; import React, { useCallback, useContext } from 'react'; -import { spliceArrayState } from '@formily/core/esm/shared/internals'; import { useTranslation } from 'react-i18next'; import { FormActiveFieldsProvider } from '../../../block-provider/hooks/useFormActiveFields'; import { useCollection } from '../../../data-source'; @@ -58,6 +58,7 @@ const ToOneNester = (props) => { const { field } = useAssociationFieldContext(); const recordV2 = useCollectionRecord(); const collection = useCollection(); + const fieldSchema = useFieldSchema(); const isAllowToSetDefaultValue = useCallback( ({ form, fieldSchema, collectionField, getInterface, formBlockType }: IsAllowToSetDefaultValueParams) => { @@ -94,7 +95,7 @@ const ToOneNester = (props) => { return ( - + {props.children} @@ -200,7 +201,7 @@ const ToManyNester = observer( )}
- + diff --git a/packages/core/client/src/schema-component/antd/association-field/SubTable.tsx b/packages/core/client/src/schema-component/antd/association-field/SubTable.tsx index 7648c6da63..6af7248181 100644 --- a/packages/core/client/src/schema-component/antd/association-field/SubTable.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/SubTable.tsx @@ -44,9 +44,6 @@ const subTableContainer = css` .ant-formily-item-error-help { display: none; } - .ant-description-textarea { - line-height: 34px; - } .ant-table-cell .ant-formily-item-error-help { display: block; position: absolute; @@ -172,7 +169,7 @@ export const SubTable: any = observer( {/* 在这里加,是为了让 “当前对象” 的配置显示正确 */} - + { - const filterFromSchema = isString(fieldSchema?.['x-component-props']?.service?.params?.filter) + const filterParams = + (isString(fieldSchema?.['x-component-props']?.service?.params?.filter) ? field.componentProps?.service?.params?.filter - : fieldSchema?.['x-component-props']?.service?.params?.filter; - - const _run = async () => { - const result = await parseFilter(mergeFilter([filterFromSchema || service?.params?.filter])); - setFieldServiceFilter(result); - }; - const run = _.debounce(_run, DEBOUNCE_WAIT); - - _run(); - - const dispose = reaction( - () => { - // 这一步主要是为了使 reaction 能够收集到依赖 - const flat = flatten(filterFromSchema, { - breakOn({ key }) { - return key.startsWith('$') && key !== '$and' && key !== '$or'; - }, - transformValue(value) { - if (!isVariable(value)) { - return value; - } - const variableName = getVariableName(value); - const variable = findVariable(variableName); - - if (process.env.NODE_ENV !== 'production' && !variable) { - throw new Error(`useServiceOptions: can not find variable ${variableName}`); - } + : fieldSchema?.['x-component-props']?.service?.params?.filter) || service?.params?.filter; - const result = getValuesByPath( - { - [variableName]: variable?.ctx || {}, - }, - getPath(value), - ); - return result; - }, - }); - return flat; - }, - run, - { - equals: _.isEqual, - }, - ); - - return dispose; - }, [ - field.componentProps?.service?.params?.filter, - fieldSchema, - findVariable, - parseFilter, - record, - service?.params?.filter, - ]); + const { filter: parsedFilterParams } = useParsedFilter({ filterOption: filterParams }); const collectionField = useMemo(() => { return getField(fieldSchema.name) || getCollectionJoinField(fieldSchema?.['x-collection-field']); @@ -143,7 +83,7 @@ export default function useServiceOptions(props) { }, } : null, - fieldServiceFilter, + parsedFilterParams, ]), isOToAny && sourceValue !== undefined && @@ -171,7 +111,7 @@ export default function useServiceOptions(props) { collectionField?.interface, collectionField?.foreignKey, fieldSchema, - fieldServiceFilter, + parsedFilterParams, sourceValue, useOriginalFilter, ]); @@ -202,12 +142,23 @@ export const useFieldNames = ( return { label: 'label', value: 'value', ...fieldNames }; }; -const SubFormContext = createContext<{ +interface SubFormProviderProps { value: any; collection: Collection; -}>(null); + /** + * the schema of the current sub-table or sub-form + */ + fieldSchema?: Schema; +} + +const SubFormContext = createContext(null); SubFormContext.displayName = 'SubFormContext'; -export const SubFormProvider = SubFormContext.Provider; + +export const SubFormProvider: FC<{ value: SubFormProviderProps }> = (props) => { + const { value, collection, fieldSchema } = props.value; + const memoValue = useMemo(() => ({ value, collection, fieldSchema }), [value, collection, fieldSchema]); + return {props.children}; +}; /** * 用于获取子表单所对应的 form 对象,其应该保持响应性,即一个 Proxy 对象; @@ -219,9 +170,10 @@ export const SubFormProvider = SubFormContext.Provider; * @returns */ export const useSubFormValue = () => { - const { value, collection } = useContext(SubFormContext) || {}; + const { value, collection, fieldSchema } = useContext(SubFormContext) || {}; return { formValue: value, collection, + fieldSchema, }; }; diff --git a/packages/core/client/src/schema-component/antd/date-picker/DatePicker.tsx b/packages/core/client/src/schema-component/antd/date-picker/DatePicker.tsx index ff6d1a58b0..02955e3314 100644 --- a/packages/core/client/src/schema-component/antd/date-picker/DatePicker.tsx +++ b/packages/core/client/src/schema-component/antd/date-picker/DatePicker.tsx @@ -9,11 +9,12 @@ import { connect, mapProps, mapReadPretty } from '@formily/react'; import { DatePicker as AntdDatePicker, DatePickerProps as AntdDatePickerProps } from 'antd'; -import React, { FC } from 'react'; +import { RangePickerProps } from 'antd/es/date-picker'; +import dayjs from 'dayjs'; +import React from 'react'; import { useTranslation } from 'react-i18next'; import { ReadPretty, ReadPrettyComposed } from './ReadPretty'; import { getDateRanges, mapDatePicker, mapRangePicker } from './util'; -import { RangePickerProps } from 'antd/es/date-picker'; interface IDatePickerProps { utc?: boolean; @@ -45,16 +46,20 @@ const InternalRangePicker = connect( mapReadPretty(ReadPretty.DateRangePicker), ); -export const DatePicker: ComposedDatePicker = (props) => { +export const DatePicker: ComposedDatePicker = (props: any) => { const { utc = true } = useDatePickerContext(); const value = Array.isArray(props.value) ? props.value[0] : props.value; - const newProps = { utc, ...props }; + const newProps = { + utc, + ...props, + showTime: props.showTime ? { defaultValue: dayjs('00:00:00', 'HH:mm:ss') } : false, + }; return ; }; DatePicker.ReadPretty = ReadPretty.DatePicker; -DatePicker.RangePicker = function RangePicker(props) { +DatePicker.RangePicker = function RangePicker(props: any) { const { t } = useTranslation(); const { utc = true } = useDatePickerContext(); const rangesValue = getDateRanges(); @@ -79,7 +84,12 @@ DatePicker.RangePicker = function RangePicker(props) { { label: t('Last 90 days'), value: rangesValue.last90Days }, { label: t('Next 90 days'), value: rangesValue.next90Days }, ]; - const newProps: any = { utc, presets, ...props }; + const newProps: any = { + utc, + presets, + ...props, + showTime: props.showTime ? { defaultValue: [dayjs('00:00:00', 'HH:mm:ss'), dayjs('00:00:00', 'HH:mm:ss')] } : false, + }; return ; }; diff --git a/packages/core/client/src/schema-component/antd/filter/FilterGroup.tsx b/packages/core/client/src/schema-component/antd/filter/FilterGroup.tsx index 08ec982e74..9aaf6fd630 100644 --- a/packages/core/client/src/schema-component/antd/filter/FilterGroup.tsx +++ b/packages/core/client/src/schema-component/antd/filter/FilterGroup.tsx @@ -95,6 +95,9 @@ export const FilterGroup = connect((props) => { field.value = { [logic]: items, }; + field.initialValue = { + [logic]: items, + }; }} > {t('Add condition')} diff --git a/packages/core/client/src/schema-component/antd/form-item/FormItem.Settings.tsx b/packages/core/client/src/schema-component/antd/form-item/FormItem.Settings.tsx index 57782fff10..788741a5c2 100644 --- a/packages/core/client/src/schema-component/antd/form-item/FormItem.Settings.tsx +++ b/packages/core/client/src/schema-component/antd/form-item/FormItem.Settings.tsx @@ -27,6 +27,7 @@ import { useOpenModeContext } from '../../../modules/popup/OpenModeProvider'; import { useRecord } from '../../../record-provider'; import { useColumnSchema } from '../../../schema-component/antd/table-v2/Table.Column.Decorator'; import { generalSettingsItems } from '../../../schema-items/GeneralSettings'; +import { getTempFieldState } from '../../../schema-settings/LinkageRules/bindLinkageRulesToFiled'; import { ActionType } from '../../../schema-settings/LinkageRules/type'; import { SchemaSettingsDataScope } from '../../../schema-settings/SchemaSettingsDataScope'; import { SchemaSettingsDateFormat } from '../../../schema-settings/SchemaSettingsDateFormat'; @@ -41,7 +42,6 @@ import { useCompile, useDesignable, useFieldModeOptions } from '../../hooks'; import { isSubMode } from '../association-field/util'; import { removeNullCondition } from '../filter'; import { DynamicComponentProps } from '../filter/DynamicComponent'; -import { getTempFieldState } from '../form-v2/utils'; import { useColorFields } from '../table-v2/Table.Column.Designer'; export const allowAddNew: SchemaSettingsItemType = { diff --git a/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx b/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx index e17b0b2f44..b909572637 100644 --- a/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx +++ b/packages/core/client/src/schema-component/antd/form-item/FormItem.tsx @@ -18,15 +18,16 @@ import { useFormActiveFields } from '../../../block-provider/hooks/useFormActive import { Collection_deprecated } from '../../../collection-manager'; import { CollectionFieldProvider } from '../../../data-source/collection-field/CollectionFieldProvider'; import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps'; +import { useDataFormItemProps } from '../../../modules/blocks/data-blocks/form/hooks/useDataFormItemProps'; import { GeneralSchemaDesigner } from '../../../schema-settings'; +import { useContextVariable, useVariables } from '../../../variables'; import { BlockItem } from '../block-item'; import { HTMLEncode } from '../input/shared'; import { FilterFormDesigner } from './FormItem.FilterFormDesigner'; import { useEnsureOperatorsValid } from './SchemaSettingOptions'; import useLazyLoadDisplayAssociationFieldsOfForm from './hooks/useLazyLoadDisplayAssociationFieldsOfForm'; +import { useLinkageRulesForSubTableOrSubForm } from './hooks/useLinkageRulesForSubTableOrSubForm'; import useParseDefaultValue from './hooks/useParseDefaultValue'; -import { useVariables, useContextVariable } from '../../../variables'; -import { useDataFormItemProps } from '../../../modules/blocks/data-blocks/form/hooks/useDataFormItemProps'; Item.displayName = 'FormilyFormItem'; @@ -61,6 +62,7 @@ export const FormItem: any = withDynamicSchemaProps( // 需要放在注冊完变量之后 useParseDefaultValue(); useLazyLoadDisplayAssociationFieldsOfForm(); + useLinkageRulesForSubTableOrSubForm(); useEffect(() => { addActiveFieldName?.(schema.name as string); diff --git a/packages/core/client/src/schema-component/antd/form-item/hooks/useLazyLoadDisplayAssociationFieldsOfForm.ts b/packages/core/client/src/schema-component/antd/form-item/hooks/useLazyLoadDisplayAssociationFieldsOfForm.ts index 170a09049a..a4f26348db 100644 --- a/packages/core/client/src/schema-component/antd/form-item/hooks/useLazyLoadDisplayAssociationFieldsOfForm.ts +++ b/packages/core/client/src/schema-component/antd/form-item/hooks/useLazyLoadDisplayAssociationFieldsOfForm.ts @@ -9,6 +9,7 @@ import { Field } from '@formily/core'; import { useField, useFieldSchema, useForm } from '@formily/react'; +import { untracked } from '@formily/reactive'; import { nextTick } from '@nocobase/utils/client'; import _ from 'lodash'; import { useEffect, useMemo, useRef } from 'react'; @@ -20,7 +21,6 @@ import { useVariables } from '../../../../variables'; import { transformVariableValue } from '../../../../variables/utils/transformVariableValue'; import { useSubFormValue } from '../../association-field/hooks'; import { isDisplayField } from '../utils'; -import { untracked } from '@formily/reactive'; /** * 用于懒加载 Form 区块中只用于展示的关联字段的值 @@ -97,7 +97,7 @@ const useLazyLoadDisplayAssociationFieldsOfForm = () => { variables .parseVariable(variableString, formVariable, { appends }) - .then((value) => { + .then(({ value }) => { nextTick(() => { const result = transformVariableValue(value, { targetCollectionField: collectionFieldRef.current }); // fix https://nocobase.height.app/T-2608 diff --git a/packages/core/client/src/schema-component/antd/form-item/hooks/useLinkageRulesForSubTableOrSubForm.ts b/packages/core/client/src/schema-component/antd/form-item/hooks/useLinkageRulesForSubTableOrSubForm.ts new file mode 100644 index 0000000000..835b5e6f3b --- /dev/null +++ b/packages/core/client/src/schema-component/antd/form-item/hooks/useLinkageRulesForSubTableOrSubForm.ts @@ -0,0 +1,90 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +/* eslint-disable react-hooks/rules-of-hooks */ +import { Field } from '@formily/core'; +import { useField, useFieldSchema } from '@formily/react'; +import { useEffect } from 'react'; +import { useFlag } from '../../../../flag-provider'; +import { bindLinkageRulesToFiled } from '../../../../schema-settings/LinkageRules/bindLinkageRulesToFiled'; +import { forEachLinkageRule } from '../../../../schema-settings/LinkageRules/forEachLinkageRule'; +import useLocalVariables from '../../../../variables/hooks/useLocalVariables'; +import useVariables from '../../../../variables/hooks/useVariables'; +import { useSubFormValue } from '../../association-field/hooks'; + +/** + * used to bind the linkage rules of the sub-table or sub-form with the current field + */ +export const useLinkageRulesForSubTableOrSubForm = () => { + const { isInSubForm, isInSubTable } = useFlag(); + + if (!isInSubForm && !isInSubTable) { + return; + } + + const field = useField(); + const fieldSchema = useFieldSchema(); + const { fieldSchema: schemaOfSubTableOrSubForm, formValue } = useSubFormValue(); + const localVariables = useLocalVariables(); + const variables = useVariables(); + + const linkageRules = getLinkageRules(schemaOfSubTableOrSubForm); + + useEffect(() => { + if (!(field.onUnmount as any).__rested) { + const _onUnmount = field.onUnmount; + field.onUnmount = () => { + (field as any).__disposes?.forEach((dispose) => { + dispose(); + }); + _onUnmount(); + }; + (field.onUnmount as any).__rested = true; + } + + if (!linkageRules) { + return; + } + + if ((field as any).__disposes) { + (field as any).__disposes.forEach((dispose) => { + dispose(); + }); + } + + const disposes = ((field as any).__disposes = []); + + forEachLinkageRule(linkageRules, (action, rule) => { + if (action.targetFields?.includes(fieldSchema.name)) { + disposes.push( + bindLinkageRulesToFiled({ + field, + linkageRules, + formValues: formValue, + localVariables, + action, + rule, + variables, + variableNameOfLeftCondition: '$iteration', + }), + ); + } + }); + + (field as any).__linkageRules = linkageRules; + }, [field, fieldSchema?.name, formValue, JSON.stringify(linkageRules), localVariables, variables]); +}; + +function getLinkageRules(fieldSchema) { + if (!fieldSchema) { + return; + } + + return fieldSchema['x-linkage-rules']?.filter((k) => !k.disabled); +} diff --git a/packages/core/client/src/schema-component/antd/form-item/hooks/useParseDefaultValue.ts b/packages/core/client/src/schema-component/antd/form-item/hooks/useParseDefaultValue.ts index a9b50e010f..d4f28748a3 100644 --- a/packages/core/client/src/schema-component/antd/form-item/hooks/useParseDefaultValue.ts +++ b/packages/core/client/src/schema-component/antd/form-item/hooks/useParseDefaultValue.ts @@ -14,6 +14,7 @@ import { getValuesByPath } from '@nocobase/utils/client'; import _ from 'lodash'; import { useCallback, useEffect } from 'react'; import { useRecordIndex } from '../../../../../src/record-provider'; +import { useFormBlockContext } from '../../../../block-provider/FormBlockProvider'; import { useCollection_deprecated } from '../../../../collection-manager'; import { useCollectionRecord } from '../../../../data-source/collection-record/CollectionRecordProvider'; import { useFlag } from '../../../../flag-provider'; @@ -39,6 +40,7 @@ const useParseDefaultValue = () => { const { getField } = useCollection_deprecated(); const { isSpecialCase, setDefaultValue } = useSpecialCase(); const index = useRecordIndex(); + const { type, form } = useFormBlockContext(); /** * name: 如 $user @@ -55,6 +57,13 @@ const useParseDefaultValue = () => { ); useEffect(() => { + // fix https://github.com/nocobase/nocobase/issues/4868 + // fix http://localhost:12000/admin/ugmnj2ycfgg/popups/1qlw5c38t3b/puid/dz42x7ffr7i/filterbytk/182 + // to clear the default value of the field + if (type === 'update' && fieldSchema.default && field.form === form) { + field.setValue?.(record?.data?.[fieldSchema.name]); + } + if ( fieldSchema.default == null || isInSetDefaultValueDialog || @@ -86,7 +95,18 @@ const useParseDefaultValue = () => { } } - const value = transformVariableValue(await variables.parseVariable(fieldSchema.default, localVariables), { + const { value: parsedValue, collectionName: collectionNameOfVariable } = await variables.parseVariable( + fieldSchema.default, + localVariables, + ); + + // fix https://tasks.aliyun.nocobase.com/admin/ugmnj2ycfgg/popups/1qlw5c38t3b/puid/dz42x7ffr7i/filterbytk/199 + if (collectionField.target && collectionField.target !== collectionNameOfVariable) { + field.loading = false; + return; + } + + const value = transformVariableValue(parsedValue, { targetCollectionField: collectionField, }); @@ -154,7 +174,7 @@ const useParseDefaultValue = () => { // 解决子表格(或子表单)中新增一行数据时,默认值不生效的问题 field.setValue(fieldSchema.default); } - }, [fieldSchema.default, localVariables]); + }, [fieldSchema.default, localVariables, type]); }; export default useParseDefaultValue; diff --git a/packages/core/client/src/schema-component/antd/form-v2/Form.tsx b/packages/core/client/src/schema-component/antd/form-v2/Form.tsx index 394dfed681..508aa9e3cb 100644 --- a/packages/core/client/src/schema-component/antd/form-v2/Form.tsx +++ b/packages/core/client/src/schema-component/antd/form-v2/Form.tsx @@ -11,27 +11,19 @@ import { css } from '@emotion/css'; import { FormLayout, IFormLayoutProps } from '@formily/antd-v5'; import { Field, Form as FormilyForm, createForm, onFieldInit, onFormInputChange } from '@formily/core'; import { FieldContext, FormContext, RecursionField, observer, useField, useFieldSchema } from '@formily/react'; -import { reaction } from '@formily/reactive'; import { uid } from '@formily/shared'; -import { getValuesByPath } from '@nocobase/utils/client'; import { ConfigProvider, Spin, theme } from 'antd'; -import _ from 'lodash'; import React, { useEffect, useMemo } from 'react'; import { useActionContext } from '..'; import { useAttach, useComponent, useDesignable } from '../..'; import { useTemplateBlockContext } from '../../../block-provider/TemplateBlockProvider'; import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps'; -import { ActionType } from '../../../schema-settings/LinkageRules/type'; +import { bindLinkageRulesToFiled } from '../../../schema-settings/LinkageRules/bindLinkageRulesToFiled'; +import { forEachLinkageRule } from '../../../schema-settings/LinkageRules/forEachLinkageRule'; import { useToken } from '../../../style'; import { useLocalVariables, useVariables } from '../../../variables'; -import { VariableOption, VariablesContextType } from '../../../variables/types'; -import { getPath } from '../../../variables/utils/getPath'; -import { getVariableName } from '../../../variables/utils/getVariableName'; -import { getVariablesFromExpression, isVariable } from '../../../variables/utils/isVariable'; -import { getInnermostKeyAndValue, getTargetField } from '../../common/utils/uitls'; import { useProps } from '../../hooks/useProps'; import { useFormBlockHeight } from './hook'; -import { collectFieldStateOfLinkageRules, getTempFieldState } from './utils'; export interface FormProps extends IFormLayoutProps { form?: FormilyForm; @@ -144,49 +136,25 @@ const WithForm = (props: WithFormProps) => { const disposes = []; form.addEffects(id, () => { - linkageRules.forEach((rule) => { - rule.actions?.forEach((action) => { - if (action.targetFields?.length) { - const fields = action.targetFields.join(','); - - // 之前使用的 `onFieldReact` 有问题,没有办法被取消监听,所以这里用 `onFieldInit` 和 `reaction` 代替 - onFieldInit(`*(${fields})`, (field: any, form) => { - field['initStateOfLinkageRules'] = { - display: field.initStateOfLinkageRules?.display || getTempFieldState(true, field.display), - required: field.initStateOfLinkageRules?.required || getTempFieldState(true, field.required || false), - pattern: field.initStateOfLinkageRules?.pattern || getTempFieldState(true, field.pattern), - value: - field.initStateOfLinkageRules?.value || getTempFieldState(true, field.value || field.initialValue), - }; - - disposes.push( - reaction( - // 这里共依赖 3 部分,当这 3 部分中的任意一部分发生变更后,需要触发联动规则: - // 1. 条件中的字段值; - // 2. 条件中的变量值; - // 3. value 表达式中的变量值; - () => { - // 获取条件中的字段值 - const fieldValuesInCondition = getFieldValuesInCondition({ linkageRules, formValues: form.values }); - - // 获取条件中的变量值 - const variableValuesInCondition = getVariableValuesInCondition({ linkageRules, localVariables }); - - // 获取 value 表达式中的变量值 - const variableValuesInExpression = getVariableValuesInExpression({ action, localVariables }); - - const result = [fieldValuesInCondition, variableValuesInCondition, variableValuesInExpression] - .map((item) => JSON.stringify(item)) - .join(','); - return result; - }, - getSubscriber(action, field, rule, variables, localVariables), - { fireImmediately: true, equals: _.isEqual }, - ), - ); - }); - } - }); + forEachLinkageRule(linkageRules, (action, rule) => { + if (action.targetFields?.length) { + const fields = action.targetFields.join(','); + + // 之前使用的 `onFieldReact` 有问题,没有办法被取消监听,所以这里用 `onFieldInit` 和 `reaction` 代替 + onFieldInit(`*(${fields})`, (field: any, form) => { + disposes.push( + bindLinkageRulesToFiled({ + field, + linkageRules, + formValues: form.values, + localVariables, + action, + rule, + variables, + }), + ); + }); + } }); }); @@ -268,165 +236,3 @@ export const Form: React.FC & { }), { displayName: 'Form' }, ); - -function getSubscriber( - action: any, - field: any, - rule: any, - variables: VariablesContextType, - localVariables: VariableOption[], -): (value: string, oldValue: string) => void { - return () => { - // 当条件改变触发 reaction 时,会同步收集字段状态,并保存到 field.stateOfLinkageRules 中 - collectFieldStateOfLinkageRules({ - operator: action.operator, - value: action.value, - field, - condition: rule.condition, - variables, - localVariables, - }); - - // 当条件改变时,有可能会触发多个 reaction,所以这里需要延迟一下,确保所有的 reaction 都执行完毕后, - // 再从 field.stateOfLinkageRules 中取值,因为此时 field.stateOfLinkageRules 中的值才是全的。 - setTimeout(async () => { - const fieldName = getFieldNameByOperator(action.operator); - - // 防止重复赋值 - if (!field.stateOfLinkageRules?.[fieldName]) { - return; - } - - let stateList = field.stateOfLinkageRules[fieldName]; - - stateList = await Promise.all(stateList); - stateList = stateList.filter((v) => v.condition); - - const lastState = stateList[stateList.length - 1]; - - if (fieldName === 'value') { - // value 比较特殊,它只有在匹配条件时才需要赋值,当条件不匹配时,维持现在的值; - // stateList 中肯定会有一个初始值,所以当 stateList.length > 1 时,就说明有匹配条件的情况; - if (stateList.length > 1) { - field.value = lastState.value; - } - } else { - field[fieldName] = lastState?.value; - requestAnimationFrame(() => { - field.setState((state) => { - state[fieldName] = lastState?.value; - }); - }); - //字段隐藏时清空数据 - if (fieldName === 'display' && lastState?.value === 'none') { - field.value = null; - } - } - // 在这里清空 field.stateOfLinkageRules,就可以保证:当条件再次改变时,如果该字段没有和任何条件匹配,则需要把对应的值恢复到初始值; - field.stateOfLinkageRules[fieldName] = null; - }); - }; -} - -function getFieldNameByOperator(operator: ActionType) { - switch (operator) { - case ActionType.Required: - case ActionType.InRequired: - return 'required'; - case ActionType.Visible: - case ActionType.None: - case ActionType.Hidden: - return 'display'; - case ActionType.Editable: - case ActionType.ReadOnly: - case ActionType.ReadPretty: - return 'pattern'; - case ActionType.Value: - return 'value'; - default: - return null; - } -} - -function getFieldValuesInCondition({ linkageRules, formValues }) { - return linkageRules.map((rule) => { - const run = (condition) => { - const type = Object.keys(condition)[0] || '$and'; - const conditions = condition[type]; - - return conditions - .map((condition) => { - // fix https://nocobase.height.app/T-3251 - if ('$and' in condition || '$or' in condition) { - return run(condition); - } - - const path = getTargetField(condition).join('.'); - return getValuesByPath(formValues, path); - }) - .filter(Boolean); - }; - - return run(rule.condition); - }); -} - -function getVariableValuesInCondition({ - linkageRules, - localVariables, -}: { - linkageRules: any[]; - localVariables: VariableOption[]; -}) { - return linkageRules.map((rule) => { - const type = Object.keys(rule.condition)[0] || '$and'; - const conditions = rule.condition[type]; - - return conditions - .map((condition) => { - const jsonlogic = getInnermostKeyAndValue(condition); - if (!jsonlogic) { - return null; - } - if (isVariable(jsonlogic.value)) { - return getVariableValue(jsonlogic.value, localVariables); - } - - return jsonlogic.value; - }) - .filter(Boolean); - }); -} - -function getVariableValuesInExpression({ action, localVariables }) { - const actionValue = action.value; - const mode = actionValue?.mode; - const value = actionValue?.value || actionValue?.result; - - if (mode !== 'express') { - return; - } - - if (value == null) { - return; - } - - return getVariablesFromExpression(value) - ?.map((variableString: string) => { - return getVariableValue(variableString, localVariables); - }) - .filter(Boolean); -} - -function getVariableValue(variableString: string, localVariables: VariableOption[]) { - if (!isVariable(variableString)) { - return; - } - - const variableName = getVariableName(variableString); - const ctx = { - [variableName]: localVariables.find((item) => item.name === variableName)?.ctx, - }; - - return getValuesByPath(ctx, getPath(variableString)); -} diff --git a/packages/core/client/src/schema-component/antd/form-v2/utils.tsx b/packages/core/client/src/schema-component/antd/form-v2/utils.tsx deleted file mode 100644 index 0525864870..0000000000 --- a/packages/core/client/src/schema-component/antd/form-v2/utils.tsx +++ /dev/null @@ -1,185 +0,0 @@ -/** - * This file is part of the NocoBase (R) project. - * Copyright (c) 2020-2024 NocoBase Co., Ltd. - * Authors: NocoBase Team. - * - * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. - * For more information, please refer to: https://www.nocobase.com/agreement. - */ - -import { Field } from '@formily/core'; -import { evaluators } from '@nocobase/evaluators/client'; -import { uid } from '@nocobase/utils/client'; -import _ from 'lodash'; -import { ActionType } from '../../../schema-settings/LinkageRules/type'; -import { VariableOption, VariablesContextType } from '../../../variables/types'; -import { REGEX_OF_VARIABLE_IN_EXPRESSION } from '../../../variables/utils/isVariable'; -import { conditionAnalyses } from '../../common/utils/uitls'; - -interface Props { - operator; - value; - field: Field & { - [key: string]: any; - }; - condition; - variables: VariablesContextType; - localVariables: VariableOption[]; -} - -/** - * 获取字段临时状态对象 - */ -export async function getTempFieldState(condition: boolean | Promise, value: any) { - [condition, value] = await Promise.all([condition, value]); - - return { - condition, - value, - }; -} - -export const collectFieldStateOfLinkageRules = ({ - operator, - value, - field, - condition, - variables, - localVariables, -}: Props) => { - const requiredResult = field?.stateOfLinkageRules?.required || [field?.initStateOfLinkageRules?.required]; - const displayResult = field?.stateOfLinkageRules?.display || [field?.initStateOfLinkageRules?.display]; - const patternResult = field?.stateOfLinkageRules?.pattern || [field?.initStateOfLinkageRules?.pattern]; - const valueResult = field?.stateOfLinkageRules?.value || [field?.initStateOfLinkageRules?.value]; - const { evaluate } = evaluators.get('formula.js'); - - switch (operator) { - case ActionType.Required: - requiredResult.push( - getTempFieldState(conditionAnalyses({ ruleGroup: condition, variables, localVariables }), true), - ); - field.stateOfLinkageRules = { - ...field.stateOfLinkageRules, - required: requiredResult, - }; - break; - case ActionType.InRequired: - requiredResult.push( - getTempFieldState(conditionAnalyses({ ruleGroup: condition, variables, localVariables }), false), - ); - field.stateOfLinkageRules = { - ...field.stateOfLinkageRules, - required: requiredResult, - }; - break; - case ActionType.Visible: - case ActionType.None: - case ActionType.Hidden: - displayResult.push( - getTempFieldState(conditionAnalyses({ ruleGroup: condition, variables, localVariables }), operator), - ); - field.stateOfLinkageRules = { - ...field.stateOfLinkageRules, - display: displayResult, - }; - break; - case ActionType.Editable: - case ActionType.ReadOnly: - case ActionType.ReadPretty: - patternResult.push( - getTempFieldState(conditionAnalyses({ ruleGroup: condition, variables, localVariables }), operator), - ); - field.stateOfLinkageRules = { - ...field.stateOfLinkageRules, - pattern: patternResult, - }; - break; - case ActionType.Value: - { - const getValue = async () => { - if (value?.mode === 'express') { - if ((value.value || value.result) == null) { - return; - } - - // 解析如 `{{$user.name}}` 之类的变量 - const { exp, scope: expScope } = await replaceVariables(value.value || value.result, { - variables, - localVariables, - }); - - try { - const result = evaluate(exp, { now: () => new Date().toString(), ...expScope }); - return result; - } catch (error) { - console.error(error); - } - } else if (value?.mode === 'constant') { - return value?.value ?? value; - } else { - return null; - } - }; - if (isConditionEmpty(condition)) { - valueResult.push(getTempFieldState(true, getValue())); - } else { - valueResult.push( - getTempFieldState(conditionAnalyses({ ruleGroup: condition, variables, localVariables }), getValue()), - ); - } - field.stateOfLinkageRules = { - ...field.stateOfLinkageRules, - value: valueResult, - }; - } - break; - default: - return null; - } -}; - -export async function replaceVariables( - value: string, - { - variables, - localVariables, - }: { - variables: VariablesContextType; - localVariables: VariableOption[]; - }, -) { - const store = {}; - const scope = {}; - - if (value == null) { - return; - } - - const waitForParsing = value.match(REGEX_OF_VARIABLE_IN_EXPRESSION)?.map(async (item) => { - const result = await variables.parseVariable(item, localVariables); - - // 在开头加 `_` 是为了保证 id 不能以数字开头,否则在解析表达式的时候(不是解析变量)会报错 - const id = `_${uid()}`; - - scope[id] = result; - store[item] = id; - return result; - }); - - if (waitForParsing) { - await Promise.all(waitForParsing); - } - return { - exp: value.replace(REGEX_OF_VARIABLE_IN_EXPRESSION, (match) => { - return `{{${store[match] || match}}}`; - }), - scope, - }; -} - -function isConditionEmpty(rules: { $and?: any; $or?: any }) { - const type = Object.keys(rules)[0] || '$and'; - const conditions = rules[type]; - - return _.isEmpty(conditions); -} diff --git a/packages/core/client/src/schema-component/antd/grid-card/GridCard.tsx b/packages/core/client/src/schema-component/antd/grid-card/GridCard.tsx index 01d5dd4aee..05777e75e7 100644 --- a/packages/core/client/src/schema-component/antd/grid-card/GridCard.tsx +++ b/packages/core/client/src/schema-component/antd/grid-card/GridCard.tsx @@ -89,6 +89,7 @@ const usePaginationProps = () => { pageSize: pageSize || 10, current: page || 1, pageSizeOptions, + showSizeChanger: true, }; } else { return { diff --git a/packages/core/client/src/schema-component/antd/list/List.Designer.tsx b/packages/core/client/src/schema-component/antd/list/List.Designer.tsx index 8042c7a1af..6933bd1c89 100644 --- a/packages/core/client/src/schema-component/antd/list/List.Designer.tsx +++ b/packages/core/client/src/schema-component/antd/list/List.Designer.tsx @@ -170,11 +170,12 @@ export const ListDesigner = () => { title={t('Records per page')} value={field.decoratorProps?.params?.pageSize || 20} options={[ + { label: '5', value: 5 }, { label: '10', value: 10 }, { label: '20', value: 20 }, { label: '50', value: 50 }, - { label: '80', value: 80 }, { label: '100', value: 100 }, + { label: '200', value: 200 }, ]} onChange={(pageSize) => { _.set(fieldSchema, 'x-decorator-props.params.pageSize', pageSize); diff --git a/packages/core/client/src/schema-component/antd/list/List.tsx b/packages/core/client/src/schema-component/antd/list/List.tsx index 4d74afcc22..53355e9d2c 100644 --- a/packages/core/client/src/schema-component/antd/list/List.tsx +++ b/packages/core/client/src/schema-component/antd/list/List.tsx @@ -51,6 +51,8 @@ const InternalList = (props) => { [fieldSchema.properties, schemaMap], ); + const pageSizeOptions = [5, 10, 20, 50, 100, 200]; + const onPaginationChange: PaginationProps['onChange'] = useCallback( (page, pageSize) => { run({ @@ -97,6 +99,8 @@ const InternalList = (props) => { total: meta?.count || 0, pageSize: meta?.pageSize || 10, current: meta?.page || 1, + showSizeChanger: true, + pageSizeOptions, } } loading={service?.loading} diff --git a/packages/core/client/src/schema-component/antd/markdown/Markdown.Void.tsx b/packages/core/client/src/schema-component/antd/markdown/Markdown.Void.tsx index 9898279885..93f4ee1009 100644 --- a/packages/core/client/src/schema-component/antd/markdown/Markdown.Void.tsx +++ b/packages/core/client/src/schema-component/antd/markdown/Markdown.Void.tsx @@ -95,7 +95,7 @@ const MarkdownEditor = (props: MarkdownEditorProps) => { - +
- + {allowConfigUI ? : null}