mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 21:49:25 +08:00
Merge remote-tracking branch 'origin/next' into event-filter
This commit is contained in:
commit
99de285b4f
119
.github/workflows/build-internal-image.yml
vendored
Normal file
119
.github/workflows/build-internal-image.yml
vendored
Normal file
@ -0,0 +1,119 @@
|
||||
name: Build Image (Internal)
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
ref_name:
|
||||
description: 'Branch or tag name to release'
|
||||
|
||||
jobs:
|
||||
get-plugins:
|
||||
uses: nocobase/nocobase/.github/workflows/get-plugins.yml@main
|
||||
secrets: inherit
|
||||
push-docker:
|
||||
runs-on: ubuntu-latest
|
||||
needs: get-plugins
|
||||
services:
|
||||
verdaccio:
|
||||
image: verdaccio/verdaccio:5
|
||||
ports:
|
||||
- 4873:4873
|
||||
steps:
|
||||
- name: Set Node.js 20
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
- name: Get info
|
||||
id: get-info
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ "${{ inputs.ref_name || github.ref_name }}" =~ "beta" ]]; then
|
||||
echo "defaultTag=$(echo 'beta')" >> $GITHUB_OUTPUT
|
||||
echo "proRepos=$(echo '${{ needs.get-plugins.outputs.beta-plugins }}')" >> $GITHUB_OUTPUT
|
||||
elif [[ "${{ inputs.ref_name || github.ref_name }}" =~ "alpha" ]]; then
|
||||
echo "defaultTag=$(echo 'alpha')" >> $GITHUB_OUTPUT
|
||||
echo "proRepos=$(echo '${{ needs.get-plugins.outputs.alpha-plugins }}')" >> $GITHUB_OUTPUT
|
||||
else
|
||||
# rc
|
||||
echo "defaultTag=$(echo 'latest')" >> $GITHUB_OUTPUT
|
||||
echo "proRepos=$(echo '${{ needs.get-plugins.outputs.rc-plugins }}')" >> $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-info.outputs.proRepos), ',') }},${{ join(fromJSON(needs.get-plugins.outputs.custom-plugins), ',') }}
|
||||
skip-token-revoke: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
ref: ${{ inputs.ref_name || github.ref_name }}
|
||||
- name: yarn install
|
||||
run: |
|
||||
yarn install
|
||||
- name: Checkout pro-plugins
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
repository: nocobase/pro-plugins
|
||||
path: packages/pro-plugins
|
||||
ref: ${{ inputs.ref_name || github.ref_name }}
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
- name: Clone pro repos
|
||||
shell: bash
|
||||
run: |
|
||||
for repo in ${{ join(fromJSON(steps.get-info.outputs.proRepos), ' ') }} ${{ join(fromJSON(needs.get-plugins.outputs.custom-plugins), ' ') }}
|
||||
do
|
||||
git clone -b ${{ inputs.ref_name || github.ref_name }} https://x-access-token:${{ steps.app-token.outputs.token }}@github.com/nocobase/$repo.git packages/pro-plugins/@nocobase/$repo
|
||||
done
|
||||
- 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: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
images: |
|
||||
nocobase/nocobase
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
- 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 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 @nocobase/plugin-notifications @nocobase/plugin-disable-pm-add $packageNames -W --production"
|
||||
APPEND_PRESET_LOCAL_PLUGINS="notifications,disable-pm-add,${pluginNames// /,}"
|
||||
echo "var1=$BEFORE_PACK_NOCOBASE" >> $GITHUB_OUTPUT
|
||||
echo "var2=$APPEND_PRESET_LOCAL_PLUGINS" >> $GITHUB_OUTPUT
|
||||
- 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: ${{ secrets.ALI_DOCKER_PUBLIC_REGISTRY }}/nocobase/nocobase:${{ steps.get-info.outputs.defaultTag }},${{ secrets.ALI_DOCKER_PUBLIC_REGISTRY }}/${{ steps.meta.outputs.tags }}
|
619
.github/workflows/manual-npm-publish-license-kit.yml
vendored
Normal file
619
.github/workflows/manual-npm-publish-license-kit.yml
vendored
Normal file
@ -0,0 +1,619 @@
|
||||
name: Manual npm publish license-kit
|
||||
env:
|
||||
DEBUG: napi:*
|
||||
APP_NAME: license-kit
|
||||
MACOSX_DEPLOYMENT_TARGET: '10.13'
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
# 'on':
|
||||
# push:
|
||||
# branches:
|
||||
# - main
|
||||
# tags-ignore:
|
||||
# - '**'
|
||||
# paths-ignore:
|
||||
# - '**/*.md'
|
||||
# - LICENSE
|
||||
# - '**/*.gitignore'
|
||||
# - .editorconfig
|
||||
# - docs/**
|
||||
# pull_request: null
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
settings:
|
||||
- host: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
build: yarn build --target x86_64-apple-darwin
|
||||
- host: windows-latest
|
||||
build: yarn build --target x86_64-pc-windows-msvc
|
||||
target: x86_64-pc-windows-msvc
|
||||
- host: windows-latest
|
||||
build: yarn build --target i686-pc-windows-msvc
|
||||
target: i686-pc-windows-msvc
|
||||
- host: ubuntu-latest
|
||||
target: x86_64-unknown-linux-gnu
|
||||
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian
|
||||
build: yarn build --target x86_64-unknown-linux-gnu
|
||||
- host: ubuntu-latest
|
||||
target: x86_64-unknown-linux-musl
|
||||
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
|
||||
build: yarn build --target x86_64-unknown-linux-musl
|
||||
- host: macos-latest
|
||||
target: aarch64-apple-darwin
|
||||
build: yarn build --target aarch64-apple-darwin
|
||||
- host: ubuntu-latest
|
||||
target: aarch64-unknown-linux-gnu
|
||||
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64
|
||||
build: yarn build --target aarch64-unknown-linux-gnu
|
||||
- host: ubuntu-latest
|
||||
target: armv7-unknown-linux-gnueabihf
|
||||
setup: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install gcc-arm-linux-gnueabihf -y
|
||||
build: yarn build --target armv7-unknown-linux-gnueabihf
|
||||
- host: ubuntu-latest
|
||||
target: armv7-unknown-linux-musleabihf
|
||||
build: yarn build --target armv7-unknown-linux-musleabihf
|
||||
- host: ubuntu-latest
|
||||
target: aarch64-linux-android
|
||||
build: yarn build --target aarch64-linux-android
|
||||
- host: ubuntu-latest
|
||||
target: armv7-linux-androideabi
|
||||
build: yarn build --target armv7-linux-androideabi
|
||||
- host: ubuntu-latest
|
||||
target: aarch64-unknown-linux-musl
|
||||
docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine
|
||||
build: |-
|
||||
set -e &&
|
||||
rustup target add aarch64-unknown-linux-musl &&
|
||||
yarn build --target aarch64-unknown-linux-musl
|
||||
- host: windows-latest
|
||||
target: aarch64-pc-windows-msvc
|
||||
build: yarn build --target aarch64-pc-windows-msvc
|
||||
- host: ubuntu-latest
|
||||
target: riscv64gc-unknown-linux-gnu
|
||||
setup: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install gcc-riscv64-linux-gnu -y
|
||||
build: yarn build --target riscv64gc-unknown-linux-gnu
|
||||
name: stable - ${{ matrix.settings.target }} - node@20
|
||||
runs-on: ${{ matrix.settings.host }}
|
||||
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: license-kit
|
||||
skip-token-revoke: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nocobase/license-kit
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
if: ${{ !matrix.settings.docker }}
|
||||
with:
|
||||
node-version: 20
|
||||
cache: yarn
|
||||
- name: Install
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
if: ${{ !matrix.settings.docker }}
|
||||
with:
|
||||
toolchain: stable
|
||||
targets: ${{ matrix.settings.target }}
|
||||
- name: Cache cargo
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
.cargo-cache
|
||||
target/
|
||||
key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.host }}
|
||||
- uses: goto-bus-stop/setup-zig@v2
|
||||
if: ${{ matrix.settings.target == 'armv7-unknown-linux-gnueabihf' || matrix.settings.target == 'armv7-unknown-linux-musleabihf' }}
|
||||
with:
|
||||
version: 0.13.0
|
||||
- name: Setup toolchain
|
||||
run: ${{ matrix.settings.setup }}
|
||||
if: ${{ matrix.settings.setup }}
|
||||
shell: bash
|
||||
- name: Setup node x86
|
||||
if: matrix.settings.target == 'i686-pc-windows-msvc'
|
||||
run: yarn config set supportedArchitectures.cpu "ia32"
|
||||
shell: bash
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
- name: Setup node x86
|
||||
uses: actions/setup-node@v4
|
||||
if: matrix.settings.target == 'i686-pc-windows-msvc'
|
||||
with:
|
||||
node-version: 20
|
||||
cache: yarn
|
||||
architecture: x86
|
||||
- name: Install OpenSSL dev
|
||||
if: ${{ matrix.settings.host == 'ubuntu-latest' }}
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y pkg-config libssl-dev
|
||||
- name: Build in docker
|
||||
uses: addnab/docker-run-action@v3
|
||||
if: ${{ matrix.settings.docker }}
|
||||
with:
|
||||
image: ${{ matrix.settings.docker }}
|
||||
options: '--user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v ${{ github.workspace }}:/build -w /build'
|
||||
run: ${{ matrix.settings.build }}
|
||||
- name: Build
|
||||
run: ${{ matrix.settings.build }}
|
||||
if: ${{ !matrix.settings.docker }}
|
||||
shell: bash
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bindings-${{ matrix.settings.target }}
|
||||
path: ${{ env.APP_NAME }}.*.node
|
||||
if-no-files-found: error
|
||||
build-freebsd:
|
||||
runs-on: ubuntu-latest
|
||||
name: Build FreeBSD
|
||||
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: license-kit
|
||||
skip-token-revoke: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nocobase/license-kit
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
- name: Build
|
||||
id: build
|
||||
uses: cross-platform-actions/action@v0.27.0
|
||||
env:
|
||||
DEBUG: napi:*
|
||||
RUSTUP_IO_THREADS: 1
|
||||
with:
|
||||
operating_system: freebsd
|
||||
version: '14.2'
|
||||
memory: 8G
|
||||
cpu_count: 3
|
||||
environment_variables: 'DEBUG RUSTUP_IO_THREADS'
|
||||
shell: bash
|
||||
run: |
|
||||
sudo pkg install -y -f curl node libnghttp2 npm openssl
|
||||
sudo npm install -g yarn --ignore-scripts
|
||||
curl https://sh.rustup.rs -sSf --output rustup.sh
|
||||
sh rustup.sh -y --profile minimal --default-toolchain beta
|
||||
source "$HOME/.cargo/env"
|
||||
echo "~~~~ rustc --version ~~~~"
|
||||
rustc --version
|
||||
echo "~~~~ node -v ~~~~"
|
||||
node -v
|
||||
echo "~~~~ yarn --version ~~~~"
|
||||
yarn --version
|
||||
pwd
|
||||
ls -lah
|
||||
whoami
|
||||
env
|
||||
freebsd-version
|
||||
yarn install
|
||||
yarn build
|
||||
rm -rf node_modules
|
||||
rm -rf target
|
||||
rm -rf .yarn/cache
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bindings-freebsd
|
||||
path: ${{ env.APP_NAME }}.*.node
|
||||
if-no-files-found: error
|
||||
test-macOS-windows-binding:
|
||||
name: Test bindings on ${{ matrix.settings.target }} - node@${{ matrix.node }}
|
||||
needs:
|
||||
- build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
settings:
|
||||
- host: macos-latest
|
||||
target: x86_64-apple-darwin
|
||||
- host: windows-latest
|
||||
target: x86_64-pc-windows-msvc
|
||||
node:
|
||||
- '18'
|
||||
- '20'
|
||||
runs-on: ${{ matrix.settings.host }}
|
||||
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: license-kit
|
||||
skip-token-revoke: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nocobase/license-kit
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: yarn
|
||||
architecture: x64
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bindings-${{ matrix.settings.target }}
|
||||
path: .
|
||||
- name: List packages
|
||||
run: ls -R .
|
||||
shell: bash
|
||||
- name: Test bindings
|
||||
run: yarn test
|
||||
test-linux-x64-gnu-binding:
|
||||
name: Test bindings on Linux-x64-gnu - node@${{ matrix.node }}
|
||||
needs:
|
||||
- build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node:
|
||||
- '18'
|
||||
- '20'
|
||||
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: license-kit
|
||||
skip-token-revoke: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nocobase/license-kit
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: yarn
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bindings-x86_64-unknown-linux-gnu
|
||||
path: .
|
||||
- name: List packages
|
||||
run: ls -R .
|
||||
shell: bash
|
||||
- name: Test bindings
|
||||
run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-slim yarn test
|
||||
test-linux-x64-musl-binding:
|
||||
name: Test bindings on x86_64-unknown-linux-musl - node@${{ matrix.node }}
|
||||
needs:
|
||||
- build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node:
|
||||
- '18'
|
||||
- '20'
|
||||
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: license-kit
|
||||
skip-token-revoke: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nocobase/license-kit
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: yarn
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn config set supportedArchitectures.libc "musl"
|
||||
yarn install
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bindings-x86_64-unknown-linux-musl
|
||||
path: .
|
||||
- name: List packages
|
||||
run: ls -R .
|
||||
shell: bash
|
||||
- name: Test bindings
|
||||
run: docker run --rm -v $(pwd):/build -w /build node:${{ matrix.node }}-alpine yarn test
|
||||
test-linux-aarch64-gnu-binding:
|
||||
name: Test bindings on aarch64-unknown-linux-gnu - node@${{ matrix.node }}
|
||||
needs:
|
||||
- build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node:
|
||||
- '18'
|
||||
- '20'
|
||||
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: license-kit
|
||||
skip-token-revoke: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nocobase/license-kit
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bindings-aarch64-unknown-linux-gnu
|
||||
path: .
|
||||
- name: List packages
|
||||
run: ls -R .
|
||||
shell: bash
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn config set supportedArchitectures.cpu "arm64"
|
||||
yarn config set supportedArchitectures.libc "glibc"
|
||||
yarn install
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: arm64
|
||||
- run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
- name: Setup and run tests
|
||||
uses: addnab/docker-run-action@v3
|
||||
with:
|
||||
image: node:${{ matrix.node }}-slim
|
||||
options: '--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build'
|
||||
run: |
|
||||
set -e
|
||||
yarn test
|
||||
ls -la
|
||||
test-linux-aarch64-musl-binding:
|
||||
name: Test bindings on aarch64-unknown-linux-musl - node@${{ matrix.node }}
|
||||
needs:
|
||||
- build
|
||||
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: license-kit
|
||||
skip-token-revoke: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nocobase/license-kit
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bindings-aarch64-unknown-linux-musl
|
||||
path: .
|
||||
- name: List packages
|
||||
run: ls -R .
|
||||
shell: bash
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn config set supportedArchitectures.cpu "arm64"
|
||||
yarn config set supportedArchitectures.libc "musl"
|
||||
yarn install
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: arm64
|
||||
- run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
- name: Setup and run tests
|
||||
uses: addnab/docker-run-action@v3
|
||||
with:
|
||||
image: node:lts-alpine
|
||||
options: '--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build'
|
||||
run: |
|
||||
set -e
|
||||
yarn test
|
||||
test-linux-arm-gnueabihf-binding:
|
||||
name: Test bindings on armv7-unknown-linux-gnueabihf - node@${{ matrix.node }}
|
||||
needs:
|
||||
- build
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
node:
|
||||
- '18'
|
||||
- '20'
|
||||
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: license-kit
|
||||
skip-token-revoke: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nocobase/license-kit
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bindings-armv7-unknown-linux-gnueabihf
|
||||
path: .
|
||||
- name: List packages
|
||||
run: ls -R .
|
||||
shell: bash
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
yarn config set supportedArchitectures.cpu "arm"
|
||||
yarn install
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
with:
|
||||
platforms: arm
|
||||
- run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
|
||||
- name: Setup and run tests
|
||||
uses: addnab/docker-run-action@v3
|
||||
with:
|
||||
image: node:${{ matrix.node }}-bullseye-slim
|
||||
options: '--platform linux/arm/v7 -v ${{ github.workspace }}:/build -w /build'
|
||||
run: |
|
||||
set -e
|
||||
yarn test
|
||||
ls -la
|
||||
universal-macOS:
|
||||
name: Build universal macOS binary
|
||||
needs:
|
||||
- build
|
||||
runs-on: macos-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: license-kit
|
||||
skip-token-revoke: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nocobase/license-kit
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: yarn
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
- name: Download macOS x64 artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bindings-x86_64-apple-darwin
|
||||
path: artifacts
|
||||
- name: Download macOS arm64 artifact
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: bindings-aarch64-apple-darwin
|
||||
path: artifacts
|
||||
- name: Combine binaries
|
||||
run: yarn universal
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: bindings-universal-apple-darwin
|
||||
path: ${{ env.APP_NAME }}.*.node
|
||||
if-no-files-found: error
|
||||
publish:
|
||||
name: Publish
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
- build-freebsd
|
||||
- test-macOS-windows-binding
|
||||
- test-linux-x64-gnu-binding
|
||||
- test-linux-x64-musl-binding
|
||||
- test-linux-aarch64-gnu-binding
|
||||
- test-linux-aarch64-musl-binding
|
||||
- test-linux-arm-gnueabihf-binding
|
||||
- universal-macOS
|
||||
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: license-kit
|
||||
skip-token-revoke: true
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: nocobase/license-kit
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
persist-credentials: true
|
||||
ref: main
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: yarn
|
||||
- name: Install dependencies
|
||||
run: yarn install
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
- name: Move artifacts
|
||||
run: yarn artifacts
|
||||
- name: List packages
|
||||
run: ls -R ./npm
|
||||
shell: bash
|
||||
- name: Publish
|
||||
run: |
|
||||
npm config set provenance true
|
||||
if git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+$";
|
||||
then
|
||||
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
|
||||
npm publish --access public
|
||||
elif git log -1 --pretty=%B | grep "^[0-9]\+\.[0-9]\+\.[0-9]\+";
|
||||
then
|
||||
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc
|
||||
npm publish --tag next --access public
|
||||
else
|
||||
echo "Not a release, skipping publish"
|
||||
fi
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
70
.github/workflows/nocobase-test-backend.yml
vendored
70
.github/workflows/nocobase-test-backend.yml
vendored
@ -5,38 +5,40 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- next
|
||||
- develop
|
||||
paths:
|
||||
- 'package.json'
|
||||
- '**/yarn.lock'
|
||||
- 'packages/core/acl/**'
|
||||
- 'packages/core/auth/**'
|
||||
- 'packages/core/actions/**'
|
||||
- 'packages/core/database/**'
|
||||
- 'packages/core/resourcer/**'
|
||||
- 'packages/core/data-source-manager/**'
|
||||
- 'packages/core/server/**'
|
||||
- 'packages/core/utils/**'
|
||||
- 'packages/plugins/**/src/server/**'
|
||||
- '.github/workflows/nocobase-test-backend.yml'
|
||||
pull_request:
|
||||
paths:
|
||||
- 'package.json'
|
||||
- '**/yarn.lock'
|
||||
- 'packages/core/acl/**'
|
||||
- 'packages/core/auth/**'
|
||||
- 'packages/core/actions/**'
|
||||
- 'packages/core/database/**'
|
||||
- 'packages/core/resourcer/**'
|
||||
- 'packages/core/data-source-manager/**'
|
||||
- 'packages/core/server/**'
|
||||
- 'packages/core/utils/**'
|
||||
- 'packages/plugins/**/src/server/**'
|
||||
- '.github/workflows/nocobase-test-backend.yml'
|
||||
workflow_dispatch:
|
||||
|
||||
# push:
|
||||
# branches:
|
||||
# - main
|
||||
# - next
|
||||
# - develop
|
||||
# paths:
|
||||
# - 'package.json'
|
||||
# - '**/yarn.lock'
|
||||
# - 'packages/core/acl/**'
|
||||
# - 'packages/core/auth/**'
|
||||
# - 'packages/core/actions/**'
|
||||
# - 'packages/core/database/**'
|
||||
# - 'packages/core/resourcer/**'
|
||||
# - 'packages/core/data-source-manager/**'
|
||||
# - 'packages/core/server/**'
|
||||
# - 'packages/core/utils/**'
|
||||
# - 'packages/plugins/**/src/server/**'
|
||||
# - '.github/workflows/nocobase-test-backend.yml'
|
||||
# pull_request:
|
||||
# paths:
|
||||
# - 'package.json'
|
||||
# - '**/yarn.lock'
|
||||
# - 'packages/core/acl/**'
|
||||
# - 'packages/core/auth/**'
|
||||
# - 'packages/core/actions/**'
|
||||
# - 'packages/core/database/**'
|
||||
# - 'packages/core/resourcer/**'
|
||||
# - 'packages/core/data-source-manager/**'
|
||||
# - 'packages/core/server/**'
|
||||
# - 'packages/core/utils/**'
|
||||
# - 'packages/plugins/**/src/server/**'
|
||||
# - '.github/workflows/nocobase-test-backend.yml'
|
||||
|
||||
jobs:
|
||||
sqlite-test:
|
||||
@ -59,7 +61,9 @@ jobs:
|
||||
node-version: ${{ matrix.node_version }}
|
||||
cache: 'yarn'
|
||||
- name: Install project dependencies
|
||||
run: yarn install
|
||||
run: |
|
||||
yarn install
|
||||
yarn add sqlite3 --no-save -W
|
||||
- name: Test with Sqlite
|
||||
run: yarn test --server --single-thread=false
|
||||
env:
|
||||
|
4
.github/workflows/nocobase-test-windows.yml
vendored
4
.github/workflows/nocobase-test-windows.yml
vendored
@ -63,7 +63,9 @@ jobs:
|
||||
${{ runner.os }}-yarn-
|
||||
|
||||
- name: Install project dependencies
|
||||
run: yarn --prefer-offline
|
||||
run: |
|
||||
yarn --prefer-offline
|
||||
yarn add sqlite3 --no-save -W
|
||||
|
||||
- name: Test with Sqlite
|
||||
run: yarn test --server --single-thread=false
|
||||
|
318
CHANGELOG.md
318
CHANGELOG.md
@ -5,6 +5,324 @@ 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).
|
||||
|
||||
## [v1.6.25](https://github.com/nocobase/nocobase/compare/v1.6.24...v1.6.25) - 2025-04-29
|
||||
|
||||
### 🎉 New Features
|
||||
|
||||
- **[undefined]** add publish ci for license kit ([#6786](https://github.com/nocobase/nocobase/pull/6786)) by @jiannx
|
||||
|
||||
- **[Data visualization: ECharts]** Add "Y-Axis inverse" setting for bar charts by @2013xile
|
||||
|
||||
### 🚀 Improvements
|
||||
|
||||
- **[utils]** Increase the height of the filter button field list and sort/categorize the fields ([#6779](https://github.com/nocobase/nocobase/pull/6779)) by @zhangzhonghe
|
||||
|
||||
- **[client]** optimize subtable add button style and align paginator on the same row ([#6790](https://github.com/nocobase/nocobase/pull/6790)) by @katherinehhh
|
||||
|
||||
- **[File manager]** Add OSS timeout option default to 10min ([#6795](https://github.com/nocobase/nocobase/pull/6795)) by @mytharcher
|
||||
|
||||
- **[Password policy]** Change default password expiration to never expire by @2013xile
|
||||
|
||||
- **[WeCom]** Prioritize corporate email over personal email when updating the user's email by @2013xile
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[client]**
|
||||
- In the collapse block, clicking the clear button in the relationship field search box should not delete the data range ([#6782](https://github.com/nocobase/nocobase/pull/6782)) by @zhangzhonghe
|
||||
|
||||
- association field not submitting data when displaying field from related collection ([#6798](https://github.com/nocobase/nocobase/pull/6798)) by @katherinehhh
|
||||
|
||||
- Prohibit moving menus before or after page tabs ([#6777](https://github.com/nocobase/nocobase/pull/6777)) by @zhangzhonghe
|
||||
|
||||
- Table block displays duplicate data when filtering ([#6792](https://github.com/nocobase/nocobase/pull/6792)) by @zhangzhonghe
|
||||
|
||||
- In the filter form, switching the field operator and then refreshing the page causes an error ([#6781](https://github.com/nocobase/nocobase/pull/6781)) by @zhangzhonghe
|
||||
|
||||
- **[database]**
|
||||
- Avoid error thrown when data type is not string ([#6797](https://github.com/nocobase/nocobase/pull/6797)) by @mytharcher
|
||||
|
||||
- add unavailableActions to sql collection and view collection ([#6765](https://github.com/nocobase/nocobase/pull/6765)) by @katherinehhh
|
||||
|
||||
- **[create-nocobase-app]** Temporarily fix mariadb issue by downgrading to 2.5.6 ([#6762](https://github.com/nocobase/nocobase/pull/6762)) by @chenos
|
||||
|
||||
- **[Authentication]** Disallow changing authenticator name ([#6808](https://github.com/nocobase/nocobase/pull/6808)) by @2013xile
|
||||
|
||||
- **[Template print]** Fix: Correct permission validation logic to prevent unauthorized actions. by @sheldon66
|
||||
|
||||
- **[File storage: S3(Pro)]** access url expiration invalid by @jiannx
|
||||
|
||||
- **[Block: Tree]** After connecting through a foreign key, clicking to trigger filtering results in empty filter conditions by @zhangzhonghe
|
||||
|
||||
## [v1.6.24](https://github.com/nocobase/nocobase/compare/v1.6.23...v1.6.24) - 2025-04-24
|
||||
|
||||
### 🚀 Improvements
|
||||
|
||||
- **[client]** Adjust upload message ([#6757](https://github.com/nocobase/nocobase/pull/6757)) by @mytharcher
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[client]**
|
||||
- only export action in view collection is support when writableView is false ([#6763](https://github.com/nocobase/nocobase/pull/6763)) by @katherinehhh
|
||||
|
||||
- unexpected association data creation when displaying association field under sub-form/sub-table in create form ([#6727](https://github.com/nocobase/nocobase/pull/6727)) by @katherinehhh
|
||||
|
||||
- Incorrect data retrieved for many-to-many array fields from related tables in forms ([#6744](https://github.com/nocobase/nocobase/pull/6744)) by @2013xile
|
||||
|
||||
## [v1.6.23](https://github.com/nocobase/nocobase/compare/v1.6.22...v1.6.23) - 2025-04-23
|
||||
|
||||
### 🚀 Improvements
|
||||
|
||||
- **[cli]** Optimize internal logic of the `nocobase upgrade` command ([#6754](https://github.com/nocobase/nocobase/pull/6754)) by @chenos
|
||||
|
||||
- **[Template print]** Replaced datasource action control with client role-based access control. by @sheldon66
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[cli]** Auto-update package.json on upgrade ([#6747](https://github.com/nocobase/nocobase/pull/6747)) by @chenos
|
||||
|
||||
- **[client]**
|
||||
- missing filter for already associated data when adding association data ([#6750](https://github.com/nocobase/nocobase/pull/6750)) by @katherinehhh
|
||||
|
||||
- tree table 'Add Child' button linkage rule missing 'current record' ([#6752](https://github.com/nocobase/nocobase/pull/6752)) by @katherinehhh
|
||||
|
||||
- **[Action: Import records]** Fix the import and export exceptions that occur when setting field permissions. ([#6677](https://github.com/nocobase/nocobase/pull/6677)) by @aaaaaajie
|
||||
|
||||
- **[Block: Gantt]** gantt chart block overlapping months in calendar header for month view ([#6753](https://github.com/nocobase/nocobase/pull/6753)) by @katherinehhh
|
||||
|
||||
- **[Action: Export records Pro]**
|
||||
- pro export button losing filter parameters after sorting table column by @katherinehhh
|
||||
|
||||
- Fix the import and export exceptions that occur when setting field permissions. by @aaaaaajie
|
||||
|
||||
- **[File storage: S3(Pro)]** Fix response data of uploaded file by @mytharcher
|
||||
|
||||
- **[Workflow: Approval]** Fix preload association fields for records by @mytharcher
|
||||
|
||||
## [v1.6.22](https://github.com/nocobase/nocobase/compare/v1.6.21...v1.6.22) - 2025-04-22
|
||||
|
||||
### 🚀 Improvements
|
||||
|
||||
- **[create-nocobase-app]** Upgrade dependencies and remove SQLite support ([#6708](https://github.com/nocobase/nocobase/pull/6708)) by @chenos
|
||||
|
||||
- **[File manager]** Expose utils API ([#6705](https://github.com/nocobase/nocobase/pull/6705)) by @mytharcher
|
||||
|
||||
- **[Workflow]** Add date types to variable types set ([#6717](https://github.com/nocobase/nocobase/pull/6717)) by @mytharcher
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[client]**
|
||||
- The problem of mobile top navigation bar icons being difficult to delete ([#6734](https://github.com/nocobase/nocobase/pull/6734)) by @zhangzhonghe
|
||||
|
||||
- After connecting through a foreign key, clicking to trigger filtering results in empty filter conditions ([#6634](https://github.com/nocobase/nocobase/pull/6634)) by @zhangzhonghe
|
||||
|
||||
- picker switching issue in date field of filter button ([#6695](https://github.com/nocobase/nocobase/pull/6695)) by @katherinehhh
|
||||
|
||||
- The issue of the collapse button in the left menu being obscured by the workflow pop-up window ([#6733](https://github.com/nocobase/nocobase/pull/6733)) by @zhangzhonghe
|
||||
|
||||
- missing action option constraints when reopening linkage rules ([#6723](https://github.com/nocobase/nocobase/pull/6723)) by @katherinehhh
|
||||
|
||||
- export button shown without export permission ([#6689](https://github.com/nocobase/nocobase/pull/6689)) by @katherinehhh
|
||||
|
||||
- Required fields hidden by linkage rules should not affect form submission ([#6709](https://github.com/nocobase/nocobase/pull/6709)) by @zhangzhonghe
|
||||
|
||||
- **[server]** appVersion incorrectly generated by create-migration ([#6740](https://github.com/nocobase/nocobase/pull/6740)) by @chenos
|
||||
|
||||
- **[build]** Fix error thrown in tar command ([#6722](https://github.com/nocobase/nocobase/pull/6722)) by @mytharcher
|
||||
|
||||
- **[Workflow]** Fix error thrown when execute schedule event in subflow ([#6721](https://github.com/nocobase/nocobase/pull/6721)) by @mytharcher
|
||||
|
||||
- **[Workflow: Custom action event]** Support to execute in multiple records mode by @mytharcher
|
||||
|
||||
- **[File storage: S3(Pro)]** Add multer make logic for server-side upload by @mytharcher
|
||||
|
||||
## [v1.6.21](https://github.com/nocobase/nocobase/compare/v1.6.20...v1.6.21) - 2025-04-17
|
||||
|
||||
### 🚀 Improvements
|
||||
|
||||
- **[client]** Add delay API for scenarios which open without delay ([#6681](https://github.com/nocobase/nocobase/pull/6681)) by @mytharcher
|
||||
|
||||
- **[create-nocobase-app]** Upgrade some dependencies to latest versions ([#6673](https://github.com/nocobase/nocobase/pull/6673)) by @chenos
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[client]**
|
||||
- Fix error thrown when mouse hover on referenced template block in approval node configuration ([#6691](https://github.com/nocobase/nocobase/pull/6691)) by @mytharcher
|
||||
|
||||
- custom association field not displaying field component settings ([#6692](https://github.com/nocobase/nocobase/pull/6692)) by @katherinehhh
|
||||
|
||||
- Fix locale for upload component ([#6682](https://github.com/nocobase/nocobase/pull/6682)) by @mytharcher
|
||||
|
||||
- lazy load missing ui component will cause render error ([#6683](https://github.com/nocobase/nocobase/pull/6683)) by @gchust
|
||||
|
||||
- Add native Password component to HoC Input ([#6679](https://github.com/nocobase/nocobase/pull/6679)) by @mytharcher
|
||||
|
||||
- inherited fields shown in current collection field assignment list ([#6666](https://github.com/nocobase/nocobase/pull/6666)) by @katherinehhh
|
||||
|
||||
- **[database]** Fixed ci build error ([#6687](https://github.com/nocobase/nocobase/pull/6687)) by @aaaaaajie
|
||||
|
||||
- **[build]** build output is incorrect when plugin depends on some AMD libraries ([#6665](https://github.com/nocobase/nocobase/pull/6665)) by @gchust
|
||||
|
||||
- **[Action: Import records]** fixed an error importing xlsx time field ([#6672](https://github.com/nocobase/nocobase/pull/6672)) by @aaaaaajie
|
||||
|
||||
- **[Workflow: Manual node]** Fix manual task status constant ([#6676](https://github.com/nocobase/nocobase/pull/6676)) by @mytharcher
|
||||
|
||||
- **[Block: iframe]** vertical scrollbar appears when iframe block is set to full height ([#6675](https://github.com/nocobase/nocobase/pull/6675)) by @katherinehhh
|
||||
|
||||
- **[Workflow: Custom action event]** Fix test cases by @mytharcher
|
||||
|
||||
- **[Backup manager]** timeout error occurs when trying to restore an unecrypted backup with a password by @gchust
|
||||
|
||||
## [v1.6.20](https://github.com/nocobase/nocobase/compare/v1.6.19...v1.6.20) - 2025-04-14
|
||||
|
||||
### 🎉 New Features
|
||||
|
||||
- **[Departments]** Make Department, Attachment URL, and Workflow response message plugins free ([#6663](https://github.com/nocobase/nocobase/pull/6663)) by @chenos
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[client]**
|
||||
- The filter form should not display the "Unsaved changes" prompt ([#6657](https://github.com/nocobase/nocobase/pull/6657)) by @zhangzhonghe
|
||||
|
||||
- "allow multiple" option not working for relation field ([#6661](https://github.com/nocobase/nocobase/pull/6661)) by @katherinehhh
|
||||
|
||||
- In the filter form, when the filter button is clicked, if there are fields that have not passed validation, the filtering is still triggered ([#6659](https://github.com/nocobase/nocobase/pull/6659)) by @zhangzhonghe
|
||||
|
||||
- Switching to the group menu should not jump to a page that has already been hidden in menu ([#6654](https://github.com/nocobase/nocobase/pull/6654)) by @zhangzhonghe
|
||||
|
||||
- **[File storage: S3(Pro)]**
|
||||
- Organize language by @jiannx
|
||||
|
||||
- Individual baseurl and public settings, improve S3 pro storage config UX by @jiannx
|
||||
|
||||
- **[Migration manager]** the skip auto backup option becomes invalid if environment variable popup appears during migration by @gchust
|
||||
|
||||
## [v1.6.19](https://github.com/nocobase/nocobase/compare/v1.6.18...v1.6.19) - 2025-04-14
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[client]**
|
||||
- Fix the issue of preview images being obscured ([#6651](https://github.com/nocobase/nocobase/pull/6651)) by @zhangzhonghe
|
||||
|
||||
- In the form block, the default value of the field configuration will first be displayed as the original variable string and then disappear ([#6649](https://github.com/nocobase/nocobase/pull/6649)) by @zhangzhonghe
|
||||
|
||||
## [v1.6.18](https://github.com/nocobase/nocobase/compare/v1.6.17...v1.6.18) - 2025-04-11
|
||||
|
||||
### 🚀 Improvements
|
||||
|
||||
- **[client]**
|
||||
- Add default type fallback API for `Variable.Input` ([#6644](https://github.com/nocobase/nocobase/pull/6644)) by @mytharcher
|
||||
|
||||
- Optimize prompts for unconfigured pages ([#6641](https://github.com/nocobase/nocobase/pull/6641)) by @zhangzhonghe
|
||||
|
||||
- **[Workflow: Delay node]** Support to use variable for duration ([#6621](https://github.com/nocobase/nocobase/pull/6621)) by @mytharcher
|
||||
|
||||
- **[Workflow: Custom action event]** Add refresh settings for trigger workflow button by @mytharcher
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[client]**
|
||||
- subtable description overlapping with add new button ([#6646](https://github.com/nocobase/nocobase/pull/6646)) by @katherinehhh
|
||||
|
||||
- dashed underline caused by horizontal form layout in modal ([#6639](https://github.com/nocobase/nocobase/pull/6639)) by @katherinehhh
|
||||
|
||||
- **[File storage: S3(Pro)]** Fix missing await for next call. by @jiannx
|
||||
|
||||
- **[Email manager]** Fix missing await for next call. by @jiannx
|
||||
|
||||
## [v1.6.17](https://github.com/nocobase/nocobase/compare/v1.6.16...v1.6.17) - 2025-04-09
|
||||
|
||||
### 🚀 Improvements
|
||||
|
||||
- **[utils]** Add duration extension for dayjs ([#6630](https://github.com/nocobase/nocobase/pull/6630)) by @mytharcher
|
||||
|
||||
- **[client]**
|
||||
- Support to search field in Filter component ([#6627](https://github.com/nocobase/nocobase/pull/6627)) by @mytharcher
|
||||
|
||||
- Add `trim` API for `Input` and `Variable.TextArea` ([#6624](https://github.com/nocobase/nocobase/pull/6624)) by @mytharcher
|
||||
|
||||
- **[Error handler]** Support custom title in AppError component. ([#6409](https://github.com/nocobase/nocobase/pull/6409)) by @sheldon66
|
||||
|
||||
- **[IP restriction]** Update IP restriction message content. by @sheldon66
|
||||
|
||||
- **[File storage: S3(Pro)]** Support global variables in storage configuration by @mytharcher
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[client]**
|
||||
- rule with 'any' condition does not take effect when condition list is empty ([#6628](https://github.com/nocobase/nocobase/pull/6628)) by @katherinehhh
|
||||
|
||||
- data issue with Gantt block in tree collection ([#6617](https://github.com/nocobase/nocobase/pull/6617)) by @katherinehhh
|
||||
|
||||
- The relationship fields in the filter form report an error after the page is refreshed because x-data-source is not carried ([#6619](https://github.com/nocobase/nocobase/pull/6619)) by @zhangzhonghe
|
||||
|
||||
- variable parse failure when URL parameters contain Chinese characters ([#6618](https://github.com/nocobase/nocobase/pull/6618)) by @katherinehhh
|
||||
|
||||
- **[Users]** Issue with parsing the user profile form schema ([#6635](https://github.com/nocobase/nocobase/pull/6635)) by @2013xile
|
||||
|
||||
- **[Mobile]** single-select field with 'contains' filter on mobile does not support multiple selection ([#6629](https://github.com/nocobase/nocobase/pull/6629)) by @katherinehhh
|
||||
|
||||
- **[Action: Export records]** missing filter params when exporting data after changing pagination ([#6633](https://github.com/nocobase/nocobase/pull/6633)) by @katherinehhh
|
||||
|
||||
- **[Email manager]** fix email management permission cannot view email list by @jiannx
|
||||
|
||||
- **[File storage: S3(Pro)]** Throw error to user when upload logo to S3 Pro storage (set to default) by @mytharcher
|
||||
|
||||
- **[Workflow: Approval]** Fix `updatedAt` changed after migration by @mytharcher
|
||||
|
||||
- **[Migration manager]** migration log creation time is displayed incorrectly in some environments by @gchust
|
||||
|
||||
## [v1.6.16](https://github.com/nocobase/nocobase/compare/v1.6.15...v1.6.16) - 2025-04-03
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[client]**
|
||||
- x-disabled property not taking effect on form fields ([#6610](https://github.com/nocobase/nocobase/pull/6610)) by @katherinehhh
|
||||
|
||||
- field label display issue to prevent truncation by colon ([#6599](https://github.com/nocobase/nocobase/pull/6599)) by @katherinehhh
|
||||
|
||||
- **[database]** When deleting one-to-many records, both `filter` and `filterByTk` are passed and `filter` includes an association field, the `filterByTk` is ignored ([#6606](https://github.com/nocobase/nocobase/pull/6606)) by @2013xile
|
||||
|
||||
## [v1.6.15](https://github.com/nocobase/nocobase/compare/v1.6.14...v1.6.15) - 2025-04-01
|
||||
|
||||
### 🚀 Improvements
|
||||
|
||||
- **[database]**
|
||||
- Add trim option for text field ([#6603](https://github.com/nocobase/nocobase/pull/6603)) by @mytharcher
|
||||
|
||||
- Add trim option for string field ([#6565](https://github.com/nocobase/nocobase/pull/6565)) by @mytharcher
|
||||
|
||||
- **[File manager]** Add trim option for text fields of storages collection ([#6604](https://github.com/nocobase/nocobase/pull/6604)) by @mytharcher
|
||||
|
||||
- **[Workflow]** Improve code ([#6589](https://github.com/nocobase/nocobase/pull/6589)) by @mytharcher
|
||||
|
||||
- **[Workflow: Approval]** Support to use block template for approval process form by @mytharcher
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
||||
- **[database]** Avoid "datetimeNoTz" field changes when value not changed in updating record ([#6588](https://github.com/nocobase/nocobase/pull/6588)) by @mytharcher
|
||||
|
||||
- **[client]**
|
||||
- association field (select) displaying N/A when exposing related collection fields ([#6582](https://github.com/nocobase/nocobase/pull/6582)) by @katherinehhh
|
||||
|
||||
- Fix `disabled` property not works when `SchemaInitializerItem` has `items` ([#6597](https://github.com/nocobase/nocobase/pull/6597)) by @mytharcher
|
||||
|
||||
- cascade issue: 'The value of xxx cannot be in array format' when deleting and re-selecting ([#6585](https://github.com/nocobase/nocobase/pull/6585)) by @katherinehhh
|
||||
|
||||
- **[Collection field: Many to many (array)]** Issue of filtering by fields in an association collection with a many to many (array) field ([#6596](https://github.com/nocobase/nocobase/pull/6596)) by @2013xile
|
||||
|
||||
- **[Public forms]** View permissions include list and get ([#6607](https://github.com/nocobase/nocobase/pull/6607)) by @chenos
|
||||
|
||||
- **[Authentication]** token assignment in `AuthProvider` ([#6593](https://github.com/nocobase/nocobase/pull/6593)) by @2013xile
|
||||
|
||||
- **[Workflow]** Fix sync option display incorrectly ([#6595](https://github.com/nocobase/nocobase/pull/6595)) by @mytharcher
|
||||
|
||||
- **[Block: Map]** map management validation should not pass with space input ([#6575](https://github.com/nocobase/nocobase/pull/6575)) by @katherinehhh
|
||||
|
||||
- **[Workflow: Approval]**
|
||||
- Fix client variables to use in approval form by @mytharcher
|
||||
|
||||
- Fix branch mode when `endOnReject` configured as `true` by @mytharcher
|
||||
|
||||
## [v1.6.14](https://github.com/nocobase/nocobase/compare/v1.6.13...v1.6.14) - 2025-03-29
|
||||
|
||||
### 🐛 Bug Fixes
|
||||
|
@ -5,6 +5,324 @@
|
||||
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
|
||||
并且本项目遵循 [语义化版本](https://semver.org/spec/v2.0.0.html)。
|
||||
|
||||
## [v1.6.25](https://github.com/nocobase/nocobase/compare/v1.6.24...v1.6.25) - 2025-04-29
|
||||
|
||||
### 🎉 新特性
|
||||
|
||||
- **[undefined]** 添加 license kit 发包ci ([#6786](https://github.com/nocobase/nocobase/pull/6786)) by @jiannx
|
||||
|
||||
- **[数据可视化:EChrats]** 条形图支持“y轴反向”设置 by @2013xile
|
||||
|
||||
### 🚀 优化
|
||||
|
||||
- **[utils]** 增加筛选按钮字段列表的高度,和对字段进行排序分类 ([#6779](https://github.com/nocobase/nocobase/pull/6779)) by @zhangzhonghe
|
||||
|
||||
- **[client]** 优化子表格添加按钮样式,并将分页器与按钮排列在同一行 ([#6790](https://github.com/nocobase/nocobase/pull/6790)) by @katherinehhh
|
||||
|
||||
- **[文件管理器]** 增加 OSS 存储引擎的超时时间配置项,且默认为 10 分钟 ([#6795](https://github.com/nocobase/nocobase/pull/6795)) by @mytharcher
|
||||
|
||||
- **[密码策略]** 默认密码过期时间修改为不过期 by @2013xile
|
||||
|
||||
- **[企业微信]** 更新用户邮箱时优先使用企业邮箱而不是个人邮箱 by @2013xile
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[client]**
|
||||
- 折叠面板区块中,当点击关系字段搜索框的清空按钮后,不应该删除数据范围 ([#6782](https://github.com/nocobase/nocobase/pull/6782)) by @zhangzhonghe
|
||||
|
||||
- 关系字段,在显示关系表下的字段数据时不提交数据 ([#6798](https://github.com/nocobase/nocobase/pull/6798)) by @katherinehhh
|
||||
|
||||
- 禁止将菜单移动到页面 tab 的前面和后面 ([#6777](https://github.com/nocobase/nocobase/pull/6777)) by @zhangzhonghe
|
||||
|
||||
- 表格区块在筛选时重复显示数据 ([#6792](https://github.com/nocobase/nocobase/pull/6792)) by @zhangzhonghe
|
||||
|
||||
- 筛选表单中,切换字段操作符后,刷新页面会报错 ([#6781](https://github.com/nocobase/nocobase/pull/6781)) by @zhangzhonghe
|
||||
|
||||
- **[database]**
|
||||
- 避免文本类型输入数据不是字符串时的报错 ([#6797](https://github.com/nocobase/nocobase/pull/6797)) by @mytharcher
|
||||
|
||||
- 补充sql collection和view collection 的unavailableActions ([#6765](https://github.com/nocobase/nocobase/pull/6765)) by @katherinehhh
|
||||
|
||||
- **[create-nocobase-app]** 回退 mariadb 版本至 2.5.6,解决兼容性问题 ([#6762](https://github.com/nocobase/nocobase/pull/6762)) by @chenos
|
||||
|
||||
- **[用户认证]** 不允许修改认证器标识 ([#6808](https://github.com/nocobase/nocobase/pull/6808)) by @2013xile
|
||||
|
||||
- **[模板打印]** 修复:修正权限校验逻辑,防止未授权操作。 by @sheldon66
|
||||
|
||||
- **[文件存储:S3 (Pro)]** 访问地址有效期无效 by @jiannx
|
||||
|
||||
- **[区块:树]** 通过外键连接后,点击触发筛选,筛选条件为空 by @zhangzhonghe
|
||||
|
||||
## [v1.6.24](https://github.com/nocobase/nocobase/compare/v1.6.23...v1.6.24) - 2025-04-24
|
||||
|
||||
### 🚀 优化
|
||||
|
||||
- **[client]** 调整上传文件的提示信息 ([#6757](https://github.com/nocobase/nocobase/pull/6757)) by @mytharcher
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[client]**
|
||||
- 视图表,无编辑权限时允许显示导出按钮 ([#6763](https://github.com/nocobase/nocobase/pull/6763)) by @katherinehhh
|
||||
|
||||
- 新增表单中显示关系字段子表格/子表单时关系数据也被新增 ([#6727](https://github.com/nocobase/nocobase/pull/6727)) by @katherinehhh
|
||||
|
||||
- 在表单中获取关联表中的多对多数组字段数据不正确 ([#6744](https://github.com/nocobase/nocobase/pull/6744)) by @2013xile
|
||||
|
||||
## [v1.6.23](https://github.com/nocobase/nocobase/compare/v1.6.22...v1.6.23) - 2025-04-23
|
||||
|
||||
### 🚀 优化
|
||||
|
||||
- **[cli]** 优化 `nocobase upgrade` 命令的内部实现逻辑 ([#6754](https://github.com/nocobase/nocobase/pull/6754)) by @chenos
|
||||
|
||||
- **[模板打印]** 用客户端角色访问控制替换了数据源操作权限控制。 by @sheldon66
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[cli]** 升级时自动更新项目的 package.json ([#6747](https://github.com/nocobase/nocobase/pull/6747)) by @chenos
|
||||
|
||||
- **[client]**
|
||||
- 添加关联表格时未过滤已关联的数据 ([#6750](https://github.com/nocobase/nocobase/pull/6750)) by @katherinehhh
|
||||
|
||||
- 树表格中添加子记录按钮的联动规则缺失「当前记录」变量 ([#6752](https://github.com/nocobase/nocobase/pull/6752)) by @katherinehhh
|
||||
|
||||
- **[操作:导入记录]** 修复设置字段权限时出现的导入导出异常。 ([#6677](https://github.com/nocobase/nocobase/pull/6677)) by @aaaaaajie
|
||||
|
||||
- **[区块:甘特图]** 甘特图区块设置月份视图时,日历头部月份重叠 ([#6753](https://github.com/nocobase/nocobase/pull/6753)) by @katherinehhh
|
||||
|
||||
- **[操作:导出记录 Pro]**
|
||||
- pro导出按钮在点击表格排序后丢失过滤参数 by @katherinehhh
|
||||
|
||||
- 修复设置字段权限时出现的导入导出异常。 by @aaaaaajie
|
||||
|
||||
- **[文件存储:S3 (Pro)]** 修复已上传文件的响应数据 by @mytharcher
|
||||
|
||||
- **[工作流:审批]** 修复预加载审批记录数据的关系字段 by @mytharcher
|
||||
|
||||
## [v1.6.22](https://github.com/nocobase/nocobase/compare/v1.6.21...v1.6.22) - 2025-04-22
|
||||
|
||||
### 🚀 优化
|
||||
|
||||
- **[create-nocobase-app]** 更新依赖,移除 SQLite 支持 ([#6708](https://github.com/nocobase/nocobase/pull/6708)) by @chenos
|
||||
|
||||
- **[文件管理器]** 暴露公共包 API ([#6705](https://github.com/nocobase/nocobase/pull/6705)) by @mytharcher
|
||||
|
||||
- **[工作流]** 为变量的类型集合增加日期相关类型 ([#6717](https://github.com/nocobase/nocobase/pull/6717)) by @mytharcher
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[client]**
|
||||
- 移动端顶部的导航栏图标很难被删除的问题 ([#6734](https://github.com/nocobase/nocobase/pull/6734)) by @zhangzhonghe
|
||||
|
||||
- 通过外键连接后,点击触发筛选,筛选条件为空 ([#6634](https://github.com/nocobase/nocobase/pull/6634)) by @zhangzhonghe
|
||||
|
||||
- 筛选按钮中日期字段,切换picker 异常 ([#6695](https://github.com/nocobase/nocobase/pull/6695)) by @katherinehhh
|
||||
|
||||
- 左侧菜单的收起按钮会被绑定工作流弹窗遮挡的问题 ([#6733](https://github.com/nocobase/nocobase/pull/6733)) by @zhangzhonghe
|
||||
|
||||
- 重新打开联动规则时缺少操作选项约束 ([#6723](https://github.com/nocobase/nocobase/pull/6723)) by @katherinehhh
|
||||
|
||||
- 未设置导出权限时仍显示导出按钮 ([#6689](https://github.com/nocobase/nocobase/pull/6689)) by @katherinehhh
|
||||
|
||||
- 被联动规则隐藏的必填字段,不应该影响表单的提交 ([#6709](https://github.com/nocobase/nocobase/pull/6709)) by @zhangzhonghe
|
||||
|
||||
- **[server]** create-migration 命令生成的 appVersion 不准确 ([#6740](https://github.com/nocobase/nocobase/pull/6740)) by @chenos
|
||||
|
||||
- **[build]** 修复 tar 命令报错的问题 ([#6722](https://github.com/nocobase/nocobase/pull/6722)) by @mytharcher
|
||||
|
||||
- **[工作流]** 修复子流程执行定时任务报错的问题 ([#6721](https://github.com/nocobase/nocobase/pull/6721)) by @mytharcher
|
||||
|
||||
- **[工作流:自定义操作事件]** 支持多行记录模式的手动执行 by @mytharcher
|
||||
|
||||
- **[文件存储:S3 (Pro)]** 增加 multer 逻辑用于服务端上传 by @mytharcher
|
||||
|
||||
## [v1.6.21](https://github.com/nocobase/nocobase/compare/v1.6.20...v1.6.21) - 2025-04-17
|
||||
|
||||
### 🚀 优化
|
||||
|
||||
- **[client]** 为弹窗组件增加 delay API ([#6681](https://github.com/nocobase/nocobase/pull/6681)) by @mytharcher
|
||||
|
||||
- **[create-nocobase-app]** 升级部分依赖的版本 ([#6673](https://github.com/nocobase/nocobase/pull/6673)) by @chenos
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[client]**
|
||||
- 修复审批节点配置中引用模板区块的添加按钮报错问题 ([#6691](https://github.com/nocobase/nocobase/pull/6691)) by @mytharcher
|
||||
|
||||
- 自定义的关系字段没有显示关系字段组件 ([#6692](https://github.com/nocobase/nocobase/pull/6692)) by @katherinehhh
|
||||
|
||||
- 修复上传组件语言问题 ([#6682](https://github.com/nocobase/nocobase/pull/6682)) by @mytharcher
|
||||
|
||||
- 懒加载组件不存在时界面报错 ([#6683](https://github.com/nocobase/nocobase/pull/6683)) by @gchust
|
||||
|
||||
- 补全原生的 Password 组件到封装过的输入组件 ([#6679](https://github.com/nocobase/nocobase/pull/6679)) by @mytharcher
|
||||
|
||||
- 字段赋值本表字段列表中显示了继承表字段,应只显示本表字段 ([#6666](https://github.com/nocobase/nocobase/pull/6666)) by @katherinehhh
|
||||
|
||||
- **[database]** 修复 CI 编译错误 ([#6687](https://github.com/nocobase/nocobase/pull/6687)) by @aaaaaajie
|
||||
|
||||
- **[build]** 插件依赖 AMD 库时构建产物不正确 ([#6665](https://github.com/nocobase/nocobase/pull/6665)) by @gchust
|
||||
|
||||
- **[操作:导入记录]** 修复导入包含时间字段的 xlsx 错误 ([#6672](https://github.com/nocobase/nocobase/pull/6672)) by @aaaaaajie
|
||||
|
||||
- **[工作流:人工处理节点]** 修复人工节点任务状态常量 ([#6676](https://github.com/nocobase/nocobase/pull/6676)) by @mytharcher
|
||||
|
||||
- **[区块:iframe]** iframe 区块设置全高时页面出现滚动条 ([#6675](https://github.com/nocobase/nocobase/pull/6675)) by @katherinehhh
|
||||
|
||||
- **[工作流:自定义操作事件]** 修复测试用例 by @mytharcher
|
||||
|
||||
- **[备份管理器]** 还原时若备份未设置密码,但用户输入了密码,还原会出现超时报错 by @gchust
|
||||
|
||||
## [v1.6.20](https://github.com/nocobase/nocobase/compare/v1.6.19...v1.6.20) - 2025-04-14
|
||||
|
||||
### 🎉 新特性
|
||||
|
||||
- **[部门]** 商业插件部门、附件 URL、工作流响应消息改为免费提供 ([#6663](https://github.com/nocobase/nocobase/pull/6663)) by @chenos
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[client]**
|
||||
- 筛选表单不应该显示“未保存修改”提示 ([#6657](https://github.com/nocobase/nocobase/pull/6657)) by @zhangzhonghe
|
||||
|
||||
- 筛选表单中关系字段的“允许多选”设置项不生效 ([#6661](https://github.com/nocobase/nocobase/pull/6661)) by @katherinehhh
|
||||
|
||||
- 筛选表单中,当点击筛选按钮时,如果有字段未校验通过,依然会触发筛选的问题 ([#6659](https://github.com/nocobase/nocobase/pull/6659)) by @zhangzhonghe
|
||||
|
||||
- 切换到分组菜单时,不应该跳转到已经在菜单中被隐藏的页面 ([#6654](https://github.com/nocobase/nocobase/pull/6654)) by @zhangzhonghe
|
||||
|
||||
- **[文件存储:S3 (Pro)]**
|
||||
- 整理语言文案 by @jiannx
|
||||
|
||||
- baseurl 和 public 设置不再互相关联,改进 S3 pro 存储的配置交互体验 by @jiannx
|
||||
|
||||
- **[迁移管理]** 迁移时若弹出环境变量弹窗,跳过自动备份选项会失效 by @gchust
|
||||
|
||||
## [v1.6.19](https://github.com/nocobase/nocobase/compare/v1.6.18...v1.6.19) - 2025-04-14
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[client]**
|
||||
- 修复预览图片被遮挡的问题 ([#6651](https://github.com/nocobase/nocobase/pull/6651)) by @zhangzhonghe
|
||||
|
||||
- 表单区块中,字段配置的默认值会先显示为原始变量字符串然后再消失 ([#6649](https://github.com/nocobase/nocobase/pull/6649)) by @zhangzhonghe
|
||||
|
||||
## [v1.6.18](https://github.com/nocobase/nocobase/compare/v1.6.17...v1.6.18) - 2025-04-11
|
||||
|
||||
### 🚀 优化
|
||||
|
||||
- **[client]**
|
||||
- 为 `Variable.Input` 组件增加默认退避类型的 API ([#6644](https://github.com/nocobase/nocobase/pull/6644)) by @mytharcher
|
||||
|
||||
- 优化未配置页面时的提示 ([#6641](https://github.com/nocobase/nocobase/pull/6641)) by @zhangzhonghe
|
||||
|
||||
- **[工作流:延时节点]** 支持延迟时间使用变量 ([#6621](https://github.com/nocobase/nocobase/pull/6621)) by @mytharcher
|
||||
|
||||
- **[工作流:自定义操作事件]** 为触发工作流按钮增加刷新配置项 by @mytharcher
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[client]**
|
||||
- 子表格中描述信息与操作按钮遮挡 ([#6646](https://github.com/nocobase/nocobase/pull/6646)) by @katherinehhh
|
||||
|
||||
- 弹窗表单在 horizontal 布局下初始宽度计算错误,导致出现提示和 下划虚线 ([#6639](https://github.com/nocobase/nocobase/pull/6639)) by @katherinehhh
|
||||
|
||||
- **[文件存储:S3 (Pro)]** 修复next调用缺少await by @jiannx
|
||||
|
||||
- **[邮件管理]** 修复next调用缺少await by @jiannx
|
||||
|
||||
## [v1.6.17](https://github.com/nocobase/nocobase/compare/v1.6.16...v1.6.17) - 2025-04-09
|
||||
|
||||
### 🚀 优化
|
||||
|
||||
- **[utils]** 为 dayjs 包增加时长扩展 ([#6630](https://github.com/nocobase/nocobase/pull/6630)) by @mytharcher
|
||||
|
||||
- **[client]**
|
||||
- 支持筛选组件中对字段进行搜索 ([#6627](https://github.com/nocobase/nocobase/pull/6627)) by @mytharcher
|
||||
|
||||
- 为 `Input` 和 `Variable.TextArea` 组件增加 `trim` API ([#6624](https://github.com/nocobase/nocobase/pull/6624)) by @mytharcher
|
||||
|
||||
- **[错误处理器]** 在 AppError 组件中支持自定义标题。 ([#6409](https://github.com/nocobase/nocobase/pull/6409)) by @sheldon66
|
||||
|
||||
- **[IP 限制]** 更新 IP 限制消息内容。 by @sheldon66
|
||||
|
||||
- **[文件存储:S3 (Pro)]** 支持存储引擎的配置中使用全局变量 by @mytharcher
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[client]**
|
||||
- 联动规则条件设置为任意且无条件内容时属性设置不生效 ([#6628](https://github.com/nocobase/nocobase/pull/6628)) by @katherinehhh
|
||||
|
||||
- 树表使用甘特图区块时数据显示异常 ([#6617](https://github.com/nocobase/nocobase/pull/6617)) by @katherinehhh
|
||||
|
||||
- 筛选表单中的关系字段在刷新页面后,由于没有携带 x-data-source 而报错 ([#6619](https://github.com/nocobase/nocobase/pull/6619)) by @zhangzhonghe
|
||||
|
||||
- 链接中中文参数变量值解析失败 ([#6618](https://github.com/nocobase/nocobase/pull/6618)) by @katherinehhh
|
||||
|
||||
- **[用户]** 用户个人资料表单 schema 的解析问题 ([#6635](https://github.com/nocobase/nocobase/pull/6635)) by @2013xile
|
||||
|
||||
- **[移动端]** 下拉单选字段在移动端设置筛选符为包含时组件未支持多选 ([#6629](https://github.com/nocobase/nocobase/pull/6629)) by @katherinehhh
|
||||
|
||||
- **[操作:导出记录]** 筛选数据后切换分页再导出时筛选参数丢失 ([#6633](https://github.com/nocobase/nocobase/pull/6633)) by @katherinehhh
|
||||
|
||||
- **[邮件管理]** 邮件管理权限无法查看邮件列表 by @jiannx
|
||||
|
||||
- **[文件存储:S3 (Pro)]** 当用户上传 logo 失败时提示错误(设置为默认存储的 S3 Pro) by @mytharcher
|
||||
|
||||
- **[工作流:审批]** 修复更新时间在迁移后变化 by @mytharcher
|
||||
|
||||
- **[迁移管理]** 部分服务器环境下迁移日志创建日期显示不正确 by @gchust
|
||||
|
||||
## [v1.6.16](https://github.com/nocobase/nocobase/compare/v1.6.15...v1.6.16) - 2025-04-03
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[client]**
|
||||
- 表单字段设置不可编辑不起作用 ([#6610](https://github.com/nocobase/nocobase/pull/6610)) by @katherinehhh
|
||||
|
||||
- 表单字段标题因冒号导致的截断问题 ([#6599](https://github.com/nocobase/nocobase/pull/6599)) by @katherinehhh
|
||||
|
||||
- **[database]** 删除一对多记录时,同时传递 `filter` 和 `filterByTk` 参数,`filter` 包含关系字段时,`filterByTk` 参数失效 ([#6606](https://github.com/nocobase/nocobase/pull/6606)) by @2013xile
|
||||
|
||||
## [v1.6.15](https://github.com/nocobase/nocobase/compare/v1.6.14...v1.6.15) - 2025-04-01
|
||||
|
||||
### 🚀 优化
|
||||
|
||||
- **[database]**
|
||||
- 为多行文本类型字段增加去除首尾空白字符的选项 ([#6603](https://github.com/nocobase/nocobase/pull/6603)) by @mytharcher
|
||||
|
||||
- 为单行文本增加自动去除首尾空白字符的选项 ([#6565](https://github.com/nocobase/nocobase/pull/6565)) by @mytharcher
|
||||
|
||||
- **[文件管理器]** 为存储引擎表的文本字段增加去除首尾空白字符的选项 ([#6604](https://github.com/nocobase/nocobase/pull/6604)) by @mytharcher
|
||||
|
||||
- **[工作流]** 优化代码 ([#6589](https://github.com/nocobase/nocobase/pull/6589)) by @mytharcher
|
||||
|
||||
- **[工作流:审批]** 支持审批表单使用区块模板 by @mytharcher
|
||||
|
||||
### 🐛 修复
|
||||
|
||||
- **[database]** 避免“日期时间(无时区)”字段在值未变动的更新时触发值改变 ([#6588](https://github.com/nocobase/nocobase/pull/6588)) by @mytharcher
|
||||
|
||||
- **[client]**
|
||||
- 关系字段(select)放出关系表字段时默认显示 N/A ([#6582](https://github.com/nocobase/nocobase/pull/6582)) by @katherinehhh
|
||||
|
||||
- 修复 `SchemaInitializerItem` 配置了 `items` 时 `disabled` 属性无效的问题 ([#6597](https://github.com/nocobase/nocobase/pull/6597)) by @mytharcher
|
||||
|
||||
- 级联组件删除后重新选择时出现 'The value of xxx cannot be in array format' ([#6585](https://github.com/nocobase/nocobase/pull/6585)) by @katherinehhh
|
||||
|
||||
- **[数据表字段:多对多 (数组)]** 主表筛选带有多对多(数组)字段的关联表中的字段报错的问题 ([#6596](https://github.com/nocobase/nocobase/pull/6596)) by @2013xile
|
||||
|
||||
- **[公开表单]** 查看权限包括 list 和 get ([#6607](https://github.com/nocobase/nocobase/pull/6607)) by @chenos
|
||||
|
||||
- **[用户认证]** `AuthProvider` 中的 token 赋值 ([#6593](https://github.com/nocobase/nocobase/pull/6593)) by @2013xile
|
||||
|
||||
- **[工作流]** 修复同步选项展示问题 ([#6595](https://github.com/nocobase/nocobase/pull/6595)) by @mytharcher
|
||||
|
||||
- **[区块:地图]** 地图管理必填校验不应通过空格输入 ([#6575](https://github.com/nocobase/nocobase/pull/6575)) by @katherinehhh
|
||||
|
||||
- **[工作流:审批]**
|
||||
- 修复审批表单中的前端变量 by @mytharcher
|
||||
|
||||
- 修复分支模式下配置拒绝则结束时的流程问题 by @mytharcher
|
||||
|
||||
## [v1.6.14](https://github.com/nocobase/nocobase/compare/v1.6.13...v1.6.14) - 2025-03-29
|
||||
|
||||
### 🐛 修复
|
||||
|
@ -1,4 +1,4 @@
|
||||
Updated Date: February 20, 2025
|
||||
Updated Date: April 1, 2025
|
||||
|
||||
NocoBase License Agreement
|
||||
|
||||
@ -88,7 +88,7 @@ Except for Third-Party Open Source Software, the Company owns all copyrights, tr
|
||||
|
||||
6.6 Can sell plugins developed for Software in the Marketplace.
|
||||
|
||||
6.7 The User with an Enterprise Edition License can sell Upper Layer Application to their clients.
|
||||
6.7 The User with a Professional or Enterprise Edition License can sell Upper Layer Application to their clients.
|
||||
|
||||
6.8 Not restricted by the AGPL-3.0 agreement.
|
||||
|
||||
@ -106,9 +106,9 @@ Except for Third-Party Open Source Software, the Company owns all copyrights, tr
|
||||
|
||||
7.4 It is not allowed to provide any form of no-code, zero-code, low-code platform SaaS products to the public using the original or modified Software.
|
||||
|
||||
7.5 It is not allowed for the User withot an Enterprise Edition license to sell Upper Layer Application to clients without a Commercial license.
|
||||
7.5 It is not allowed for the User withot a Professional or Enterprise Edition license to sell Upper Layer Application to clients without a Commercial license.
|
||||
|
||||
7.6 It is not allowed for the User with an Enterprise Edition license to sell Upper Layer Application to clients without a Commercial license with access to further development and configuration.
|
||||
7.6 It is not allowed for the User with a Professional or Enterprise Edition license to sell Upper Layer Application to clients without a Commercial license with access to further development and configuration.
|
||||
|
||||
7.7 It is not allowed to publicly sell plugins developed for Software outside of the Marketplace.
|
||||
|
||||
|
@ -2,14 +2,10 @@
|
||||
|
||||
https://github.com/user-attachments/assets/cf08bfe5-e6e6-453c-8b96-350a6a8bed17
|
||||
|
||||
## ご協力ありがとうございます!
|
||||
<p align="center">
|
||||
<a href="https://trendshift.io/repositories/4112" target="_blank"><img src="https://trendshift.io/api/badge/repositories/4112" alt="nocobase%2Fnocobase | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
|
||||
<a href="https://www.producthunt.com/posts/nocobase?embed=true&utm_source=badge-top-post-topic-badge&utm_medium=badge&utm_souce=badge-nocobase" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-topic-badge.svg?post_id=456520&theme=light&period=weekly&topic_id=267" alt="NocoBase - Scalability-first, open-source no-code platform | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
|
||||
## リリースノート
|
||||
|
||||
リリースノートは[ブログ](https://www.nocobase.com/ja/blog/timeline)で随時更新され、週ごとにまとめて公開しています。
|
||||
</p>
|
||||
|
||||
## NocoBaseはなに?
|
||||
|
||||
@ -28,6 +24,16 @@ https://docs-cn.nocobase.com/
|
||||
コミュニティ:
|
||||
https://forum.nocobase.com/
|
||||
|
||||
チュートリアル:
|
||||
https://www.nocobase.com/ja/tutorials
|
||||
|
||||
顧客のストーリー:
|
||||
https://www.nocobase.com/ja/blog/tags/customer-stories
|
||||
|
||||
## リリースノート
|
||||
|
||||
リリースノートは[ブログ](https://www.nocobase.com/ja/blog/timeline)で随時更新され、週ごとにまとめて公開しています。
|
||||
|
||||
## 他の製品との違い
|
||||
|
||||
### 1. データモデル駆動
|
||||
|
22
README.md
22
README.md
@ -2,19 +2,14 @@ English | [中文](./README.zh-CN.md) | [日本語](./README.ja-JP.md)
|
||||
|
||||
https://github.com/user-attachments/assets/a50c100a-4561-4e06-b2d2-d48098659ec0
|
||||
|
||||
## We'd love your support!
|
||||
|
||||
<p align="center">
|
||||
<a href="https://trendshift.io/repositories/4112" target="_blank"><img src="https://trendshift.io/api/badge/repositories/4112" alt="nocobase%2Fnocobase | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
|
||||
<a href="https://www.producthunt.com/posts/nocobase?embed=true&utm_source=badge-top-post-topic-badge&utm_medium=badge&utm_souce=badge-nocobase" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-topic-badge.svg?post_id=456520&theme=light&period=weekly&topic_id=267" alt="NocoBase - Scalability-first, open-source no-code platform | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
|
||||
## Release Notes
|
||||
|
||||
Our [blog](https://www.nocobase.com/en/blog/timeline) is regularly updated with release notes and provides a weekly summary.
|
||||
</p>
|
||||
|
||||
## What is NocoBase
|
||||
|
||||
NocoBase is a scalability-first, open-source no-code development platform.
|
||||
NocoBase is an extensibility-first, open-source no-code development platform.
|
||||
Instead of investing years of time and millions of dollars in research and development, deploy NocoBase in a few minutes and you'll have a private, controllable, and extremely scalable no-code development platform!
|
||||
|
||||
Homepage:
|
||||
@ -29,6 +24,17 @@ https://docs.nocobase.com/
|
||||
Forum:
|
||||
https://forum.nocobase.com/
|
||||
|
||||
Tutorials:
|
||||
https://www.nocobase.com/en/tutorials
|
||||
|
||||
Use Cases:
|
||||
https://www.nocobase.com/en/blog/tags/customer-stories
|
||||
|
||||
|
||||
## Release Notes
|
||||
|
||||
Our [blog](https://www.nocobase.com/en/blog/timeline) is regularly updated with release notes and provides a weekly summary.
|
||||
|
||||
## Distinctive features
|
||||
|
||||
### 1. Data model-driven
|
||||
|
@ -2,13 +2,10 @@
|
||||
|
||||
https://github.com/nocobase/nocobase/assets/1267426/29623e45-9a48-4598-bb9e-9dd173ade553
|
||||
|
||||
## 感谢支持
|
||||
<p align="center">
|
||||
<a href="https://trendshift.io/repositories/4112" target="_blank"><img src="https://trendshift.io/api/badge/repositories/4112" alt="nocobase%2Fnocobase | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
|
||||
|
||||
<a href="https://www.producthunt.com/posts/nocobase?embed=true&utm_source=badge-top-post-topic-badge&utm_medium=badge&utm_souce=badge-nocobase" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-topic-badge.svg?post_id=456520&theme=light&period=weekly&topic_id=267" alt="NocoBase - Scalability-first, open-source no-code platform | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
|
||||
## 发布日志
|
||||
我们的[博客](https://www.nocobase.com/cn/blog/timeline)会及时更新发布日志,并每周进行汇总。
|
||||
</p>
|
||||
|
||||
## NocoBase 是什么
|
||||
|
||||
@ -27,6 +24,15 @@ https://docs-cn.nocobase.com/
|
||||
社区:
|
||||
https://forum.nocobase.com/
|
||||
|
||||
教程:
|
||||
https://www.nocobase.com/cn/tutorials
|
||||
|
||||
用户故事:
|
||||
https://www.nocobase.com/cn/blog/tags/customer-stories
|
||||
|
||||
## 发布日志
|
||||
我们的[博客](https://www.nocobase.com/cn/blog/timeline)会及时更新发布日志,并每周进行汇总。
|
||||
|
||||
## 与众不同之处
|
||||
|
||||
### 1. 数据模型驱动
|
||||
|
@ -6,9 +6,13 @@ WORKDIR /app
|
||||
|
||||
RUN cd /app \
|
||||
&& yarn config set network-timeout 600000 -g \
|
||||
&& npx -y create-nocobase-app@${CNA_VERSION} my-nocobase-app -a -e APP_ENV=production \
|
||||
&& npx -y create-nocobase-app@${CNA_VERSION} my-nocobase-app --skip-dev-dependencies -a -e APP_ENV=production \
|
||||
&& cd /app/my-nocobase-app \
|
||||
&& yarn install --production
|
||||
&& yarn install --production \
|
||||
&& rm -rf yarn.lock \
|
||||
&& find node_modules -type f -name "yarn.lock" -delete \
|
||||
&& find node_modules -type f -name "bower.json" -delete \
|
||||
&& find node_modules -type f -name "composer.json" -delete
|
||||
|
||||
RUN cd /app \
|
||||
&& rm -rf nocobase.tar.gz \
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "1.7.0-beta.12",
|
||||
"version": "1.7.0-beta.26",
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"npmClientArgs": ["--ignore-engines"],
|
||||
|
@ -83,6 +83,7 @@
|
||||
"ghooks": "^2.0.4",
|
||||
"lint-staged": "^13.2.3",
|
||||
"patch-package": "^8.0.0",
|
||||
"pm2": "^6.0.5",
|
||||
"pretty-format": "^24.0.0",
|
||||
"pretty-quick": "^3.1.0",
|
||||
"react": "^18.0.0",
|
||||
|
@ -1,13 +1,13 @@
|
||||
{
|
||||
"name": "@nocobase/acl",
|
||||
"version": "1.7.0-beta.12",
|
||||
"version": "1.7.0-beta.26",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/resourcer": "1.7.0-beta.12",
|
||||
"@nocobase/utils": "1.7.0-beta.12",
|
||||
"@nocobase/resourcer": "1.7.0-beta.26",
|
||||
"@nocobase/utils": "1.7.0-beta.26",
|
||||
"minimatch": "^5.1.1"
|
||||
},
|
||||
"repository": {
|
||||
|
@ -30,9 +30,65 @@ export function mergeRole(roles: ACLRole[]) {
|
||||
}
|
||||
}
|
||||
result.snippets = mergeRoleSnippets(allSnippets);
|
||||
adjustActionByStrategy(roles, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* When merging permissions from multiple roles, if strategy.actions allows certain actions, then those actions have higher priority.
|
||||
* For example, [
|
||||
* {
|
||||
* actions: {
|
||||
* 'users:view': {...},
|
||||
* 'users:create': {...}
|
||||
* },
|
||||
* strategy: {
|
||||
* actions: ['view']
|
||||
* }
|
||||
* }]
|
||||
* finally result: [{
|
||||
* actions: {
|
||||
* 'users:create': {...},
|
||||
* 'users:view': {} // all view
|
||||
* },
|
||||
* {
|
||||
* strategy: {
|
||||
* actions: ['view']
|
||||
* }]
|
||||
**/
|
||||
function adjustActionByStrategy(
|
||||
roles,
|
||||
result: {
|
||||
actions?: Record<string, object>;
|
||||
strategy?: { actions?: string[] };
|
||||
resources?: string[];
|
||||
},
|
||||
) {
|
||||
const { actions, strategy } = result;
|
||||
const actionSet = getAdjustActions(roles);
|
||||
if (!_.isEmpty(actions) && !_.isEmpty(strategy?.actions) && !_.isEmpty(result.resources)) {
|
||||
for (const resource of result.resources) {
|
||||
for (const action of strategy.actions) {
|
||||
if (actionSet.has(action)) {
|
||||
actions[`${resource}:${action}`] = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getAdjustActions(roles: ACLRole[]) {
|
||||
const actionSet = new Set<string>();
|
||||
for (const role of roles) {
|
||||
const jsonRole = role.toJSON();
|
||||
// Within the same role, actions have higher priority than strategy.actions.
|
||||
if (!_.isEmpty(jsonRole.strategy?.['actions']) && _.isEmpty(jsonRole.actions)) {
|
||||
jsonRole.strategy['actions'].forEach((x) => !x.includes('own') && actionSet.add(x));
|
||||
}
|
||||
}
|
||||
return actionSet;
|
||||
}
|
||||
|
||||
function mergeRoleNames(sourceRoleNames, newRoleName) {
|
||||
return newRoleName ? sourceRoleNames.concat(newRoleName) : sourceRoleNames;
|
||||
}
|
||||
|
@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "@nocobase/actions",
|
||||
"version": "1.7.0-beta.12",
|
||||
"version": "1.7.0-beta.26",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/cache": "1.7.0-beta.12",
|
||||
"@nocobase/database": "1.7.0-beta.12",
|
||||
"@nocobase/resourcer": "1.7.0-beta.12"
|
||||
"@nocobase/cache": "1.7.0-beta.26",
|
||||
"@nocobase/database": "1.7.0-beta.26",
|
||||
"@nocobase/resourcer": "1.7.0-beta.26"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -1,17 +1,17 @@
|
||||
{
|
||||
"name": "@nocobase/app",
|
||||
"version": "1.7.0-beta.12",
|
||||
"version": "1.7.0-beta.26",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/database": "1.7.0-beta.12",
|
||||
"@nocobase/preset-nocobase": "1.7.0-beta.12",
|
||||
"@nocobase/server": "1.7.0-beta.12"
|
||||
"@nocobase/database": "1.7.0-beta.26",
|
||||
"@nocobase/preset-nocobase": "1.7.0-beta.26",
|
||||
"@nocobase/server": "1.7.0-beta.26"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nocobase/client": "1.7.0-beta.12"
|
||||
"@nocobase/client": "1.7.0-beta.26"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -1,18 +1,18 @@
|
||||
{
|
||||
"name": "@nocobase/auth",
|
||||
"version": "1.7.0-beta.12",
|
||||
"version": "1.7.0-beta.26",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/actions": "1.7.0-beta.12",
|
||||
"@nocobase/cache": "1.7.0-beta.12",
|
||||
"@nocobase/database": "1.7.0-beta.12",
|
||||
"@nocobase/resourcer": "1.7.0-beta.12",
|
||||
"@nocobase/utils": "1.7.0-beta.12",
|
||||
"@types/jsonwebtoken": "^8.5.8",
|
||||
"jsonwebtoken": "^8.5.1"
|
||||
"@nocobase/actions": "1.7.0-beta.26",
|
||||
"@nocobase/cache": "1.7.0-beta.26",
|
||||
"@nocobase/database": "1.7.0-beta.26",
|
||||
"@nocobase/resourcer": "1.7.0-beta.26",
|
||||
"@nocobase/utils": "1.7.0-beta.26",
|
||||
"@types/jsonwebtoken": "^9.0.9",
|
||||
"jsonwebtoken": "^9.0.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/build",
|
||||
"version": "1.7.0-beta.12",
|
||||
"version": "1.7.0-beta.26",
|
||||
"description": "Library build tool based on rollup.",
|
||||
"main": "lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
@ -17,7 +17,7 @@
|
||||
"@lerna/project": "4.0.0",
|
||||
"@rsbuild/plugin-babel": "^1.0.3",
|
||||
"@rsdoctor/rspack-plugin": "^0.4.8",
|
||||
"@rspack/core": "1.1.1",
|
||||
"@rspack/core": "1.3.2",
|
||||
"@svgr/webpack": "^8.1.0",
|
||||
"@types/gulp": "^4.0.13",
|
||||
"@types/lerna__package": "5.1.0",
|
||||
@ -39,7 +39,7 @@
|
||||
"postcss-preset-env": "^9.1.2",
|
||||
"react-imported-component": "^6.5.4",
|
||||
"style-loader": "^3.3.3",
|
||||
"tar": "^6.2.0",
|
||||
"tar": "^7.4.3",
|
||||
"tsup": "8.2.4",
|
||||
"typescript": "5.1.3",
|
||||
"update-notifier": "3.0.0",
|
||||
|
@ -347,6 +347,7 @@ export async function buildPluginClient(cwd: string, userConfig: UserConfig, sou
|
||||
umdNamedDefine: true,
|
||||
},
|
||||
},
|
||||
amd: {},
|
||||
resolve: {
|
||||
tsConfig: path.join(process.cwd(), 'tsconfig.json'),
|
||||
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json', '.less', '.css'],
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import tar from 'tar';
|
||||
import { create } from 'tar';
|
||||
import fg from 'fast-glob';
|
||||
import fs from 'fs-extra';
|
||||
|
||||
@ -38,5 +38,5 @@ export function tarPlugin(cwd: string, log: PkgLog) {
|
||||
|
||||
fs.mkdirpSync(path.dirname(tarball));
|
||||
fs.rmSync(tarball, { force: true });
|
||||
return tar.c({ gzip: true, file: tarball, cwd }, tarFiles);
|
||||
return create({ gzip: true, file: tarball, cwd }, tarFiles);
|
||||
}
|
||||
|
4
packages/core/cache/package.json
vendored
4
packages/core/cache/package.json
vendored
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@nocobase/cache",
|
||||
"version": "1.7.0-beta.12",
|
||||
"version": "1.7.0-beta.26",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./lib/index.js",
|
||||
"types": "./lib/index.d.ts",
|
||||
"dependencies": {
|
||||
"@nocobase/lock-manager": "1.7.0-beta.12",
|
||||
"@nocobase/lock-manager": "1.7.0-beta.26",
|
||||
"bloom-filters": "^3.0.1",
|
||||
"cache-manager": "^5.2.4",
|
||||
"cache-manager-redis-yet": "^4.1.2"
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/cli",
|
||||
"version": "1.7.0-beta.12",
|
||||
"version": "1.7.0-beta.26",
|
||||
"description": "",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "./src/index.js",
|
||||
@ -8,24 +8,25 @@
|
||||
"nocobase": "./bin/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nocobase/app": "1.7.0-beta.12",
|
||||
"@nocobase/app": "1.7.0-beta.26",
|
||||
"@types/fs-extra": "^11.0.1",
|
||||
"@umijs/utils": "3.5.20",
|
||||
"chalk": "^4.1.1",
|
||||
"commander": "^9.2.0",
|
||||
"deepmerge": "^4.3.1",
|
||||
"dotenv": "^16.0.0",
|
||||
"execa": "^5.1.1",
|
||||
"fast-glob": "^3.3.1",
|
||||
"fs-extra": "^11.1.1",
|
||||
"p-all": "3.0.0",
|
||||
"pm2": "^5.2.0",
|
||||
"pm2": "^6.0.5",
|
||||
"portfinder": "^1.0.28",
|
||||
"serve": "^13.0.2",
|
||||
"tar": "^7.4.3",
|
||||
"tree-kill": "^1.2.2",
|
||||
"tsx": "^4.19.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nocobase/devtools": "1.7.0-beta.12"
|
||||
"@nocobase/devtools": "1.7.0-beta.26"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
const _ = require('lodash');
|
||||
const { Command } = require('commander');
|
||||
const { generatePlugins, run, postCheck, nodeCheck, promptForTs, isPortReachable } = require('../util');
|
||||
const { generatePlugins, run, postCheck, nodeCheck, promptForTs, isPortReachable, checkDBDialect } = require('../util');
|
||||
const { getPortPromise } = require('portfinder');
|
||||
const chokidar = require('chokidar');
|
||||
const { uid } = require('@formily/shared');
|
||||
@ -36,6 +36,7 @@ module.exports = (cli) => {
|
||||
.option('-i, --inspect [port]')
|
||||
.allowUnknownOption()
|
||||
.action(async (opts) => {
|
||||
checkDBDialect();
|
||||
let subprocess;
|
||||
const runDevClient = () => {
|
||||
console.log('starting client', 1 * clientPort);
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
const { Command } = require('commander');
|
||||
const { run, isPortReachable } = require('../util');
|
||||
const { run, isPortReachable, checkDBDialect } = require('../util');
|
||||
const { execSync } = require('node:child_process');
|
||||
const axios = require('axios');
|
||||
const { pTest } = require('./p-test');
|
||||
@ -165,6 +165,7 @@ const filterArgv = () => {
|
||||
*/
|
||||
module.exports = (cli) => {
|
||||
const e2e = cli.command('e2e').hook('preAction', () => {
|
||||
checkDBDialect();
|
||||
if (process.env.APP_BASE_URL) {
|
||||
process.env.APP_BASE_URL = process.env.APP_BASE_URL.replace('localhost', '127.0.0.1');
|
||||
console.log('APP_BASE_URL:', process.env.APP_BASE_URL);
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
const { Command } = require('commander');
|
||||
const { run, isDev, isProd, promptForTs, downloadPro } = require('../util');
|
||||
const { run, isDev, isProd, promptForTs, downloadPro, checkDBDialect } = require('../util');
|
||||
|
||||
/**
|
||||
*
|
||||
@ -21,6 +21,7 @@ module.exports = (cli) => {
|
||||
.option('-h, --help')
|
||||
.option('--ts-node-dev')
|
||||
.action(async (options) => {
|
||||
checkDBDialect();
|
||||
const cmd = process.argv.slice(2)?.[0];
|
||||
if (cmd === 'install') {
|
||||
await downloadPro();
|
||||
|
@ -30,6 +30,7 @@ module.exports = (cli) => {
|
||||
require('./test')(cli);
|
||||
require('./test-coverage')(cli);
|
||||
require('./umi')(cli);
|
||||
require('./update-deps')(cli);
|
||||
require('./upgrade')(cli);
|
||||
require('./postinstall')(cli);
|
||||
require('./pkg')(cli);
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
const _ = require('lodash');
|
||||
const { Command } = require('commander');
|
||||
const { run, postCheck, downloadPro, promptForTs } = require('../util');
|
||||
const { run, postCheck, downloadPro, promptForTs, checkDBDialect } = require('../util');
|
||||
const { existsSync, rmSync } = require('fs');
|
||||
const { resolve, isAbsolute } = require('path');
|
||||
const chalk = require('chalk');
|
||||
@ -48,8 +48,10 @@ module.exports = (cli) => {
|
||||
.option('-i, --instances [instances]')
|
||||
.option('--db-sync')
|
||||
.option('--quickstart')
|
||||
.option('--launch-mode [launchMode]')
|
||||
.allowUnknownOption()
|
||||
.action(async (opts) => {
|
||||
checkDBDialect();
|
||||
if (opts.quickstart) {
|
||||
await downloadPro();
|
||||
}
|
||||
@ -118,6 +120,8 @@ module.exports = (cli) => {
|
||||
]);
|
||||
process.exit();
|
||||
} else {
|
||||
const launchMode = opts.launchMode || process.env.APP_LAUNCH_MODE || 'pm2';
|
||||
if (launchMode === 'pm2') {
|
||||
run(
|
||||
'pm2-runtime',
|
||||
[
|
||||
@ -129,6 +133,14 @@ module.exports = (cli) => {
|
||||
...process.argv.slice(2),
|
||||
].filter(Boolean),
|
||||
);
|
||||
} else {
|
||||
run(
|
||||
'node',
|
||||
[`${APP_PACKAGE_ROOT}/lib/index.js`, ...(NODE_ARGS || '').split(' '), ...process.argv.slice(2)].filter(
|
||||
Boolean,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -7,7 +7,7 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
const { run } = require('../util');
|
||||
const { run, checkDBDialect } = require('../util');
|
||||
const fg = require('fast-glob');
|
||||
|
||||
const coreClientPackages = ['packages/core/client', 'packages/core/sdk'];
|
||||
@ -30,6 +30,7 @@ const getPackagesDir = (isClient) => {
|
||||
|
||||
module.exports = (cli) => {
|
||||
cli.command('test-coverage:server').action(async () => {
|
||||
checkDBDialect();
|
||||
const packageRoots = getPackagesDir(false);
|
||||
for (const dir of packageRoots) {
|
||||
try {
|
||||
@ -41,6 +42,7 @@ module.exports = (cli) => {
|
||||
});
|
||||
|
||||
cli.command('test-coverage:client').action(async () => {
|
||||
checkDBDialect();
|
||||
const packageRoots = getPackagesDir(true);
|
||||
for (const dir of packageRoots) {
|
||||
try {
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
const { Command } = require('commander');
|
||||
const { run } = require('../util');
|
||||
const { run, checkDBDialect } = require('../util');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
@ -29,6 +29,7 @@ function addTestCommand(name, cli) {
|
||||
.arguments('[paths...]')
|
||||
.allowUnknownOption()
|
||||
.action(async (paths, opts) => {
|
||||
checkDBDialect();
|
||||
if (name === 'test:server') {
|
||||
process.env.TEST_ENV = 'server-side';
|
||||
} else if (name === 'test:client') {
|
||||
|
71
packages/core/cli/src/commands/update-deps.js
Normal file
71
packages/core/cli/src/commands/update-deps.js
Normal file
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const chalk = require('chalk');
|
||||
const { Command } = require('commander');
|
||||
const { resolve } = require('path');
|
||||
const { run, promptForTs, runAppCommand, hasCorePackages, downloadPro, hasTsNode, checkDBDialect } = require('../util');
|
||||
const { existsSync, rmSync } = require('fs');
|
||||
const { readJSON, writeJSON } = require('fs-extra');
|
||||
const deepmerge = require('deepmerge');
|
||||
|
||||
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 });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Command} cli
|
||||
*/
|
||||
module.exports = (cli) => {
|
||||
cli
|
||||
.command('update-deps')
|
||||
.option('--force')
|
||||
.allowUnknownOption()
|
||||
.action(async (options) => {
|
||||
if (hasCorePackages() || !hasTsNode()) {
|
||||
await downloadPro();
|
||||
return;
|
||||
}
|
||||
const pkg = require('../../package.json');
|
||||
let distTag = 'latest';
|
||||
if (pkg.version.includes('alpha')) {
|
||||
distTag = 'alpha';
|
||||
} else if (pkg.version.includes('beta')) {
|
||||
distTag = 'beta';
|
||||
}
|
||||
const { stdout } = await run('npm', ['info', `@nocobase/cli@${distTag}`, 'version'], {
|
||||
stdio: 'pipe',
|
||||
});
|
||||
if (!options.force && pkg.version === stdout) {
|
||||
await downloadPro();
|
||||
rmAppDir();
|
||||
return;
|
||||
}
|
||||
const descPath = resolve(process.cwd(), 'package.json');
|
||||
const descJson = await readJSON(descPath, 'utf8');
|
||||
const sourcePath = resolve(__dirname, '../../templates/create-app-package.json');
|
||||
const sourceJson = await readJSON(sourcePath, 'utf8');
|
||||
if (descJson['dependencies']?.['@nocobase/cli']) {
|
||||
descJson['dependencies']['@nocobase/cli'] = stdout;
|
||||
}
|
||||
if (descJson['devDependencies']?.['@nocobase/devtools']) {
|
||||
descJson['devDependencies']['@nocobase/devtools'] = stdout;
|
||||
}
|
||||
const json = deepmerge(descJson, sourceJson);
|
||||
await writeJSON(descPath, json, { spaces: 2, encoding: 'utf8' });
|
||||
await run('yarn', ['install']);
|
||||
await downloadPro();
|
||||
rmAppDir();
|
||||
});
|
||||
};
|
@ -10,15 +10,25 @@
|
||||
const chalk = require('chalk');
|
||||
const { Command } = require('commander');
|
||||
const { resolve } = require('path');
|
||||
const { run, promptForTs, runAppCommand, hasCorePackages, downloadPro, hasTsNode } = require('../util');
|
||||
const { run, promptForTs, runAppCommand, hasCorePackages, downloadPro, hasTsNode, checkDBDialect } = require('../util');
|
||||
const { existsSync, rmSync } = require('fs');
|
||||
const { readJSON, writeJSON } = require('fs-extra');
|
||||
const deepmerge = require('deepmerge');
|
||||
|
||||
async function updatePackage() {
|
||||
const sourcePath = resolve(__dirname, '../../templates/create-app-package.json');
|
||||
const descPath = resolve(process.cwd(), 'package.json');
|
||||
const sourceJson = await readJSON(sourcePath, 'utf8');
|
||||
const descJson = await readJSON(descPath, 'utf8');
|
||||
const json = deepmerge(descJson, sourceJson);
|
||||
await writeJSON(descPath, json, { spaces: 2, encoding: 'utf8' });
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {Command} cli
|
||||
*/
|
||||
module.exports = (cli) => {
|
||||
const { APP_PACKAGE_ROOT } = process.env;
|
||||
cli
|
||||
.command('upgrade')
|
||||
.allowUnknownOption()
|
||||
@ -26,52 +36,12 @@ module.exports = (cli) => {
|
||||
.option('--next')
|
||||
.option('-S|--skip-code-update')
|
||||
.action(async (options) => {
|
||||
if (hasTsNode()) promptForTs();
|
||||
if (hasCorePackages()) {
|
||||
// await run('yarn', ['install']);
|
||||
await downloadPro();
|
||||
await runAppCommand('upgrade');
|
||||
return;
|
||||
}
|
||||
checkDBDialect();
|
||||
if (options.skipCodeUpdate) {
|
||||
await downloadPro();
|
||||
await runAppCommand('upgrade');
|
||||
return;
|
||||
} else {
|
||||
await run('nocobase', ['update-deps']);
|
||||
await run('nocobase', ['upgrade', '--skip-code-update']);
|
||||
}
|
||||
// await runAppCommand('upgrade');
|
||||
if (!hasTsNode()) {
|
||||
await downloadPro();
|
||||
await runAppCommand('upgrade');
|
||||
return;
|
||||
}
|
||||
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');
|
||||
let distTag = 'latest';
|
||||
if (pkg.version.includes('alpha')) {
|
||||
distTag = 'alpha';
|
||||
} else if (pkg.version.includes('beta')) {
|
||||
distTag = 'beta';
|
||||
}
|
||||
// get latest version
|
||||
const { stdout } = await run('npm', ['info', `@nocobase/cli@${distTag}`, 'version'], {
|
||||
stdio: 'pipe',
|
||||
});
|
||||
if (pkg.version === stdout) {
|
||||
await downloadPro();
|
||||
await runAppCommand('upgrade');
|
||||
await rmAppDir();
|
||||
return;
|
||||
}
|
||||
await run('yarn', ['add', `@nocobase/cli@${distTag}`, `@nocobase/devtools@${distTag}`, '-W']);
|
||||
await run('yarn', ['install']);
|
||||
await downloadPro();
|
||||
await runAppCommand('upgrade');
|
||||
await rmAppDir();
|
||||
});
|
||||
};
|
||||
|
@ -360,7 +360,7 @@ exports.initEnv = function initEnv() {
|
||||
API_BASE_PATH: '/api/',
|
||||
API_CLIENT_STORAGE_PREFIX: 'NOCOBASE_',
|
||||
API_CLIENT_STORAGE_TYPE: 'localStorage',
|
||||
DB_DIALECT: 'sqlite',
|
||||
// DB_DIALECT: 'sqlite',
|
||||
DB_STORAGE: 'storage/db/nocobase.sqlite',
|
||||
// DB_TIMEZONE: '+00:00',
|
||||
DB_UNDERSCORED: parseEnv('DB_UNDERSCORED'),
|
||||
@ -460,8 +460,22 @@ exports.initEnv = function initEnv() {
|
||||
process.env.SOCKET_PATH = generateGatewayPath();
|
||||
fs.mkdirpSync(dirname(process.env.SOCKET_PATH), { force: true, recursive: true });
|
||||
fs.mkdirpSync(process.env.PM2_HOME, { force: true, recursive: true });
|
||||
const pkgDir = resolve(process.cwd(), 'storage/plugins', '@nocobase/plugin-multi-app-manager');
|
||||
fs.existsSync(pkgDir) && fs.rmdirSync(pkgDir, { force: true });
|
||||
const pkgs = [
|
||||
'@nocobase/plugin-multi-app-manager',
|
||||
'@nocobase/plugin-departments',
|
||||
'@nocobase/plugin-field-attachment-url',
|
||||
'@nocobase/plugin-workflow-response-message',
|
||||
];
|
||||
for (const pkg of pkgs) {
|
||||
const pkgDir = resolve(process.cwd(), 'storage/plugins', pkg);
|
||||
fs.existsSync(pkgDir) && fs.rmdirSync(pkgDir, { recursive: true, force: true });
|
||||
}
|
||||
};
|
||||
|
||||
exports.checkDBDialect = function () {
|
||||
if (!process.env.DB_DIALECT) {
|
||||
throw new Error('DB_DIALECT is required.');
|
||||
}
|
||||
};
|
||||
|
||||
exports.generatePlugins = function () {
|
||||
|
39
packages/core/cli/templates/create-app-package.json
Normal file
39
packages/core/cli/templates/create-app-package.json
Normal file
@ -0,0 +1,39 @@
|
||||
{
|
||||
"private": true,
|
||||
"workspaces": ["packages/*/*", "packages/*/*/*"],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"scripts": {
|
||||
"nocobase": "nocobase",
|
||||
"pm": "nocobase pm",
|
||||
"pm2": "nocobase pm2",
|
||||
"dev": "nocobase dev",
|
||||
"start": "nocobase start",
|
||||
"clean": "nocobase clean",
|
||||
"build": "nocobase build",
|
||||
"test": "nocobase test",
|
||||
"e2e": "nocobase e2e",
|
||||
"tar": "nocobase tar",
|
||||
"postinstall": "nocobase postinstall",
|
||||
"lint": "eslint ."
|
||||
},
|
||||
"resolutions": {
|
||||
"cytoscape": "3.28.0",
|
||||
"@types/react": "18.3.18",
|
||||
"@types/react-dom": "^18.0.0",
|
||||
"react-router-dom": "6.28.1",
|
||||
"react-router": "6.28.1",
|
||||
"async": "^3.2.6",
|
||||
"antd": "5.12.8",
|
||||
"rollup": "4.24.0",
|
||||
"semver": "^7.7.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"pm2": "^6.0.5",
|
||||
"mysql2": "^3.14.0",
|
||||
"mariadb": "^2.5.6",
|
||||
"pg": "^8.14.1",
|
||||
"pg-hstore": "^2.3.4"
|
||||
}
|
||||
}
|
@ -248,6 +248,10 @@ export default defineConfig({
|
||||
"title": "Filter",
|
||||
"link": "/components/filter"
|
||||
},
|
||||
{
|
||||
"title": "LinkageFilter",
|
||||
"link": "/components/linkage-filter"
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@nocobase/client",
|
||||
"version": "1.7.0-beta.12",
|
||||
"version": "1.7.0-beta.26",
|
||||
"license": "AGPL-3.0",
|
||||
"main": "lib/index.js",
|
||||
"module": "es/index.mjs",
|
||||
@ -27,9 +27,9 @@
|
||||
"@formily/reactive-react": "^2.2.27",
|
||||
"@formily/shared": "^2.2.27",
|
||||
"@formily/validator": "^2.2.27",
|
||||
"@nocobase/evaluators": "1.7.0-beta.12",
|
||||
"@nocobase/sdk": "1.7.0-beta.12",
|
||||
"@nocobase/utils": "1.7.0-beta.12",
|
||||
"@nocobase/evaluators": "1.7.0-beta.26",
|
||||
"@nocobase/sdk": "1.7.0-beta.26",
|
||||
"@nocobase/utils": "1.7.0-beta.26",
|
||||
"ahooks": "^3.7.2",
|
||||
"antd": "5.24.2",
|
||||
"antd-style": "3.7.1",
|
||||
|
@ -314,15 +314,17 @@ export const ACLActionProvider = (props) => {
|
||||
const schema = useFieldSchema();
|
||||
const currentUid = schema['x-uid'];
|
||||
let actionPath = schema['x-acl-action'];
|
||||
const editablePath = ['create', 'update', 'destroy', 'importXlsx'];
|
||||
// 只兼容这些数据表资源按钮
|
||||
const resourceActionPath = ['create', 'update', 'destroy', 'importXlsx', 'export'];
|
||||
// 视图表无编辑权限时不支持的操作
|
||||
const writableViewCollectionAction = ['create', 'update', 'destroy', 'importXlsx', 'bulkDestroy', 'bulkUpdate'];
|
||||
|
||||
if (!actionPath && resource && schema['x-action'] && editablePath.includes(schema['x-action'])) {
|
||||
if (!actionPath && resource && schema['x-action'] && resourceActionPath.includes(schema['x-action'])) {
|
||||
actionPath = `${resource}:${schema['x-action']}`;
|
||||
}
|
||||
if (actionPath && !actionPath?.includes(':')) {
|
||||
actionPath = `${resource}:${actionPath}`;
|
||||
}
|
||||
|
||||
const params = useMemo(
|
||||
() => actionPath && parseAction(actionPath, { schema, recordPkValue }),
|
||||
[parseAction, actionPath, schema, recordPkValue],
|
||||
@ -339,16 +341,18 @@ export const ACLActionProvider = (props) => {
|
||||
if (!params) {
|
||||
return <ACLActionParamsContext.Provider value={params}>{props.children}</ACLActionParamsContext.Provider>;
|
||||
}
|
||||
//视图表无编辑权限时不显示
|
||||
if (editablePath.includes(actionPath) || editablePath.includes(actionPath?.split(':')[1])) {
|
||||
//视图表无编辑权限时不支持 writableViewCollectionAction 的按钮
|
||||
if (
|
||||
writableViewCollectionAction.includes(actionPath) ||
|
||||
writableViewCollectionAction.includes(actionPath?.split(':')[1])
|
||||
) {
|
||||
if ((collection && collection.template !== 'view') || collection?.writableView) {
|
||||
return <ACLActionParamsContext.Provider value={params}>{props.children}</ACLActionParamsContext.Provider>;
|
||||
}
|
||||
return null;
|
||||
return <ACLActionParamsContext.Provider value={false}>{props.children}</ACLActionParamsContext.Provider>;
|
||||
}
|
||||
return <ACLActionParamsContext.Provider value={params}>{props.children}</ACLActionParamsContext.Provider>;
|
||||
};
|
||||
|
||||
export const useACLFieldWhitelist = () => {
|
||||
const params = useContext(ACLActionParamsContext);
|
||||
const whitelist = useMemo(() => {
|
||||
|
@ -236,7 +236,6 @@ export const scopesSchema: ISchema = {
|
||||
'x-component': 'Action.Link',
|
||||
'x-component-props': {
|
||||
openMode: 'drawer',
|
||||
icon: 'EditOutlined',
|
||||
},
|
||||
properties: {
|
||||
drawer: {
|
||||
|
@ -150,6 +150,9 @@ export class APIClient extends APIClientSDK {
|
||||
}
|
||||
return [{ message }];
|
||||
}
|
||||
if (error?.response?.data?.error) {
|
||||
return [error?.response?.data?.error];
|
||||
}
|
||||
return (
|
||||
error?.response?.data?.errors ||
|
||||
error?.response?.data?.messages ||
|
||||
|
@ -72,7 +72,7 @@ describe('SchemaInitializerDivider', () => {
|
||||
expect(onSubmit).toBeCalled();
|
||||
});
|
||||
|
||||
it('item mode', async () => {
|
||||
it.skip('item mode', async () => {
|
||||
const onSubmit = vitest.fn();
|
||||
const Demo = () => {
|
||||
return (
|
||||
|
@ -11,10 +11,11 @@ import React, { FC } from 'react';
|
||||
import { MainComponent } from './MainComponent';
|
||||
|
||||
const Loading: FC = () => <div>Loading...</div>;
|
||||
const AppError: FC<{ error: Error }> = ({ error }) => {
|
||||
const AppError: FC<{ error: Error & { title?: string } }> = ({ error }) => {
|
||||
const title = error?.title || 'App Error';
|
||||
return (
|
||||
<div>
|
||||
<div>App Error</div>
|
||||
<div>{title}</div>
|
||||
{error?.message}
|
||||
{process.env.__TEST__ && error?.stack}
|
||||
</div>
|
||||
|
@ -167,7 +167,7 @@ export function getOperators() {
|
||||
const dateA = parseDate(a);
|
||||
const dateB = parseDate(b);
|
||||
if (!dateA || !dateB) {
|
||||
throw new Error('Invalid date format');
|
||||
return false;
|
||||
}
|
||||
return dateA < dateB;
|
||||
},
|
||||
@ -651,10 +651,11 @@ function parseYear(dateStr) {
|
||||
}
|
||||
|
||||
function parseDate(targetDateStr) {
|
||||
let dateStr = Array.isArray(targetDateStr) ? targetDateStr[1] : targetDateStr;
|
||||
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z$/.test(dateStr)) {
|
||||
// ISO 8601 格式:YYYY-MM-DDTHH:mm:ss.sssZ
|
||||
return new Date(dateStr); // 直接解析为 Date 对象
|
||||
let dateStr = Array.isArray(targetDateStr) ? targetDateStr[1] ?? targetDateStr[0] : targetDateStr;
|
||||
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(dateStr)) {
|
||||
return new Date(dateStr);
|
||||
} else if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(dateStr)) {
|
||||
return new Date(dateStr.replace(' ', 'T'));
|
||||
} else if (/^\d{4}-\d{2}-\d{2}$/.test(dateStr)) {
|
||||
// YYYY-MM-DD 格式
|
||||
return parseFullDate(dateStr);
|
||||
@ -668,5 +669,6 @@ function parseDate(targetDateStr) {
|
||||
// YYYY 格式
|
||||
return parseYear(dateStr);
|
||||
}
|
||||
return null; // Invalid format
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -63,7 +63,7 @@ export const SchemaInitializerItem = memo(
|
||||
className: className,
|
||||
label: children || compile(title),
|
||||
onClick: (info) => {
|
||||
if (info.key !== name) return;
|
||||
if (disabled || info.key !== name) return;
|
||||
if (closeInitializerMenuWhenClick) {
|
||||
setVisible?.(false);
|
||||
}
|
||||
@ -73,10 +73,10 @@ export const SchemaInitializerItem = memo(
|
||||
children: childrenItems,
|
||||
},
|
||||
];
|
||||
}, [name, style, className, children, title, onClick, icon, childrenItems]);
|
||||
}, [name, disabled, style, className, children, title, onClick, icon, childrenItems]);
|
||||
|
||||
if (items && items.length > 0) {
|
||||
return <SchemaInitializerMenu items={menuItems}></SchemaInitializerMenu>;
|
||||
return <SchemaInitializerMenu disabled={disabled} items={menuItems}></SchemaInitializerMenu>;
|
||||
}
|
||||
return (
|
||||
<div
|
||||
|
@ -34,20 +34,6 @@ export interface SchemaSettingsChildrenProps {
|
||||
children: SchemaSettingsItemType[];
|
||||
}
|
||||
|
||||
const typeComponentMap = {
|
||||
item: SchemaSettingsItem,
|
||||
itemGroup: SchemaSettingsItemGroup,
|
||||
subMenu: SchemaSettingsSubMenu,
|
||||
divider: SchemaSettingsDivider,
|
||||
remove: SchemaSettingsRemove,
|
||||
select: SchemaSettingsSelectItem,
|
||||
cascader: SchemaSettingsCascaderItem,
|
||||
switch: SchemaSettingsSwitchItem,
|
||||
popup: SchemaSettingsPopupItem,
|
||||
actionModal: SchemaSettingsActionModalItem,
|
||||
modal: SchemaSettingsModalItem,
|
||||
};
|
||||
|
||||
const SchemaSettingsChildErrorFallback: FC<
|
||||
FallbackProps & {
|
||||
title: string;
|
||||
@ -113,6 +99,19 @@ export const SchemaSettingsChild: FC<SchemaSettingsItemType> = (props) => {
|
||||
hideIfNoChildren,
|
||||
componentProps,
|
||||
} = props as any;
|
||||
const typeComponentMap = {
|
||||
item: SchemaSettingsItem,
|
||||
itemGroup: SchemaSettingsItemGroup,
|
||||
subMenu: SchemaSettingsSubMenu,
|
||||
divider: SchemaSettingsDivider,
|
||||
remove: SchemaSettingsRemove,
|
||||
select: SchemaSettingsSelectItem,
|
||||
cascader: SchemaSettingsCascaderItem,
|
||||
switch: SchemaSettingsSwitchItem,
|
||||
popup: SchemaSettingsPopupItem,
|
||||
actionModal: SchemaSettingsActionModalItem,
|
||||
modal: SchemaSettingsModalItem,
|
||||
};
|
||||
const useChildrenRes = useChildren();
|
||||
const useComponentPropsRes = useComponentProps();
|
||||
const findComponent = useFindComponent();
|
||||
|
@ -22,8 +22,15 @@ import { useCreateFormBlockProps } from '../modules/blocks/data-blocks/form/hook
|
||||
import { useEditFormBlockDecoratorProps } from '../modules/blocks/data-blocks/form/hooks/useEditFormBlockDecoratorProps';
|
||||
import { useEditFormBlockProps } from '../modules/blocks/data-blocks/form/hooks/useEditFormBlockProps';
|
||||
import { useDataFormItemProps } from '../modules/blocks/data-blocks/form/hooks/useDataFormItemProps';
|
||||
import { useGridCardBlockDecoratorProps } from '../modules/blocks/data-blocks/grid-card/hooks/useGridCardBlockDecoratorProps';
|
||||
import { useListBlockDecoratorProps } from '../modules/blocks/data-blocks/list/hooks/useListBlockDecoratorProps';
|
||||
import {
|
||||
useGridCardBlockDecoratorProps,
|
||||
useGridCardBlockItemProps,
|
||||
useGridCardBlockProps,
|
||||
} from '../modules/blocks/data-blocks/grid-card/hooks/useGridCardBlockDecoratorProps';
|
||||
import {
|
||||
useListBlockDecoratorProps,
|
||||
useListBlockProps,
|
||||
} from '../modules/blocks/data-blocks/list/hooks/useListBlockDecoratorProps';
|
||||
import { useTableSelectorDecoratorProps } from '../modules/blocks/data-blocks/table-selector/hooks/useTableSelectorDecoratorProps';
|
||||
import { TableColumnSchemaToolbar } from '../modules/blocks/data-blocks/table/TableColumnSchemaToolbar';
|
||||
import { useTableBlockDecoratorProps } from '../modules/blocks/data-blocks/table/hooks/useTableBlockDecoratorProps';
|
||||
@ -80,11 +87,14 @@ export const BlockSchemaComponentProvider: React.FC = (props) => {
|
||||
useTableSelectorProps,
|
||||
useTableBlockDecoratorProps,
|
||||
useListBlockDecoratorProps,
|
||||
useListBlockProps,
|
||||
useTableSelectorDecoratorProps,
|
||||
useCollapseBlockDecoratorProps,
|
||||
useFilterFormBlockProps,
|
||||
useFilterFormBlockDecoratorProps,
|
||||
useGridCardBlockDecoratorProps,
|
||||
useGridCardBlockItemProps,
|
||||
useGridCardBlockProps,
|
||||
useFormItemProps,
|
||||
useDataFormItemProps,
|
||||
}}
|
||||
@ -141,11 +151,14 @@ export class BlockSchemaComponentPlugin extends Plugin {
|
||||
useTableSelectorProps,
|
||||
useTableBlockDecoratorProps,
|
||||
useListBlockDecoratorProps,
|
||||
useListBlockProps,
|
||||
useTableSelectorDecoratorProps,
|
||||
useCollapseBlockDecoratorProps,
|
||||
useFilterFormBlockProps,
|
||||
useFilterFormBlockDecoratorProps,
|
||||
useGridCardBlockDecoratorProps,
|
||||
useGridCardBlockProps,
|
||||
useGridCardBlockItemProps,
|
||||
useFormItemProps,
|
||||
useDataFormItemProps,
|
||||
});
|
||||
|
@ -10,11 +10,11 @@
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import React from 'react';
|
||||
import { withDynamicSchemaProps } from '../hoc/withDynamicSchemaProps';
|
||||
import { DatePickerProvider, ActionBarProvider, SchemaComponentOptions } from '../schema-component';
|
||||
import { FilterCollectionField } from '../modules/blocks/filter-blocks/FilterCollectionField';
|
||||
import { ActionBarProvider, DatePickerProvider, SchemaComponentOptions } from '../schema-component';
|
||||
import { DefaultValueProvider } from '../schema-settings';
|
||||
import { CollectOperators } from './CollectOperators';
|
||||
import { FormBlockProvider } from './FormBlockProvider';
|
||||
import { FilterCollectionField } from '../modules/blocks/filter-blocks/FilterCollectionField';
|
||||
|
||||
export const FilterFormBlockProvider = withDynamicSchemaProps((props) => {
|
||||
const filedSchema = useFieldSchema();
|
||||
@ -35,7 +35,7 @@ export const FilterFormBlockProvider = withDynamicSchemaProps((props) => {
|
||||
}}
|
||||
>
|
||||
<DefaultValueProvider isAllowToSetDefaultValue={() => false}>
|
||||
<FormBlockProvider name="filter-form" {...props}></FormBlockProvider>
|
||||
<FormBlockProvider name="filter-form" {...props} confirmBeforeClose={false}></FormBlockProvider>
|
||||
</DefaultValueProvider>
|
||||
</ActionBarProvider>
|
||||
</DatePickerProvider>
|
||||
|
@ -50,6 +50,7 @@ const InternalFormBlockProvider = (props) => {
|
||||
const form = useMemo(
|
||||
() =>
|
||||
createForm({
|
||||
validateFirst: true,
|
||||
readPretty,
|
||||
}),
|
||||
[readPretty],
|
||||
|
@ -52,6 +52,7 @@ import { useBlockRequestContext, useFilterByTk, useParamsFromRecord } from '../B
|
||||
import { useOperators } from '../CollectOperators';
|
||||
import { useDetailsBlockContext } from '../DetailsBlockProvider';
|
||||
import { TableFieldResource } from '../TableFieldProvider';
|
||||
import { getVariableValue } from '../../common/getVariableValue';
|
||||
|
||||
export * from './useBlockHeightProps';
|
||||
export * from './useDataBlockParentRecord';
|
||||
@ -97,6 +98,35 @@ const filterValue = (value) => {
|
||||
return obj;
|
||||
};
|
||||
|
||||
function getFilteredFormValues(form) {
|
||||
const values = _.cloneDeep(form.values);
|
||||
const allFields = [];
|
||||
form.query('*').forEach((field) => {
|
||||
if (field) {
|
||||
allFields.push(field);
|
||||
}
|
||||
});
|
||||
const readonlyPaths = _.uniq(
|
||||
allFields
|
||||
.filter((field) => field?.componentProps?.readOnlySubmit)
|
||||
.map((field) => {
|
||||
const segments = field.path?.segments || [];
|
||||
if (segments.length <= 1) {
|
||||
return segments.join('.');
|
||||
}
|
||||
return segments.slice(0, -1).join('.');
|
||||
}),
|
||||
);
|
||||
readonlyPaths.forEach((path, index) => {
|
||||
if (index !== 0 || path.includes('.')) {
|
||||
// 清空值,但跳过第一层
|
||||
_.unset(values, path);
|
||||
}
|
||||
});
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
export function getFormValues({
|
||||
filterByTk,
|
||||
field,
|
||||
@ -124,7 +154,7 @@ export function getFormValues({
|
||||
}
|
||||
}
|
||||
|
||||
return form.values;
|
||||
return getFilteredFormValues(form);
|
||||
}
|
||||
|
||||
export function useCollectValuesToSubmit() {
|
||||
@ -203,12 +233,6 @@ export function useCollectValuesToSubmit() {
|
||||
]);
|
||||
}
|
||||
|
||||
function interpolateVariables(str: string, scope: Record<string, any>): string {
|
||||
return str.replace(/\{\{\s*([a-zA-Z0-9_$-.]+?)\s*\}\}/g, (_, key) => {
|
||||
return scope[key] !== undefined ? String(scope[key]) : '';
|
||||
});
|
||||
}
|
||||
|
||||
export const useCreateActionProps = () => {
|
||||
const filterByTk = useFilterByTk();
|
||||
const record = useCollectionRecord();
|
||||
@ -258,11 +282,10 @@ export const useCreateActionProps = () => {
|
||||
});
|
||||
let redirectTo = rawRedirectTo;
|
||||
if (rawRedirectTo) {
|
||||
const { exp, scope: expScope } = await replaceVariables(rawRedirectTo, {
|
||||
redirectTo = await getVariableValue(rawRedirectTo, {
|
||||
variables,
|
||||
localVariables: [...localVariables, { name: '$record', ctx: new Proxy(data?.data?.data, {}) }],
|
||||
});
|
||||
redirectTo = interpolateVariables(exp, expScope);
|
||||
}
|
||||
|
||||
if (actionAfterSuccess === 'previous' || (!actionAfterSuccess && redirecting !== true)) {
|
||||
@ -546,9 +569,11 @@ export const useFilterBlockActionProps = () => {
|
||||
const { doFilter } = useDoFilter();
|
||||
const actionField = useField();
|
||||
actionField.data = actionField.data || {};
|
||||
const form = useForm();
|
||||
|
||||
return {
|
||||
async onClick() {
|
||||
await form.submit();
|
||||
actionField.data.loading = true;
|
||||
await doFilter();
|
||||
actionField.data.loading = false;
|
||||
@ -655,11 +680,11 @@ export const useCustomizeUpdateActionProps = () => {
|
||||
|
||||
let redirectTo = rawRedirectTo;
|
||||
if (rawRedirectTo) {
|
||||
const { exp, scope: expScope } = await replaceVariables(rawRedirectTo, {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
redirectTo = await getVariableValue(rawRedirectTo, {
|
||||
variables,
|
||||
localVariables: [...localVariables, { name: '$record', ctx: new Proxy(result?.data?.data?.[0], {}) }],
|
||||
localVariables: [...localVariables, { name: '$record', ctx: new Proxy(result?.data?.data, {}) }],
|
||||
});
|
||||
redirectTo = interpolateVariables(exp, expScope);
|
||||
}
|
||||
|
||||
if (actionAfterSuccess === 'previous' || (!actionAfterSuccess && redirecting !== true)) {
|
||||
@ -1019,11 +1044,11 @@ export const useUpdateActionProps = () => {
|
||||
}
|
||||
let redirectTo = rawRedirectTo;
|
||||
if (rawRedirectTo) {
|
||||
const { exp, scope: expScope } = await replaceVariables(rawRedirectTo, {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
redirectTo = await getVariableValue(rawRedirectTo, {
|
||||
variables,
|
||||
localVariables: [...localVariables, { name: '$record', ctx: new Proxy(result?.data?.data?.[0], {}) }],
|
||||
localVariables: [...localVariables, { name: '$record', ctx: new Proxy(result?.data?.data, {}) }],
|
||||
});
|
||||
redirectTo = interpolateVariables(exp, expScope);
|
||||
}
|
||||
|
||||
if (actionAfterSuccess === 'previous' || (!actionAfterSuccess && redirecting !== true)) {
|
||||
@ -1482,6 +1507,7 @@ export const useAssociationFilterBlockProps = () => {
|
||||
run,
|
||||
valueKey,
|
||||
labelKey,
|
||||
dataScopeFilter: filter,
|
||||
};
|
||||
};
|
||||
async function doReset({
|
||||
@ -1580,7 +1606,7 @@ export const getAppends = ({
|
||||
const fieldNames = getTargetField(item);
|
||||
|
||||
// 只应该收集关系字段,只有大于 1 的时候才是关系字段
|
||||
if (fieldNames.length > 1) {
|
||||
if (fieldNames.length > 1 && !item.op) {
|
||||
appends.add(fieldNames.join('.'));
|
||||
}
|
||||
});
|
||||
|
@ -62,6 +62,12 @@ export class InputFieldInterface extends CollectionFieldInterface {
|
||||
hasDefaultValue = true;
|
||||
properties = {
|
||||
...defaultProps,
|
||||
trim: {
|
||||
type: 'boolean',
|
||||
'x-content': '{{t("Automatically remove heading and tailing spaces")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
},
|
||||
layout: {
|
||||
type: 'void',
|
||||
title: '{{t("Index")}}',
|
||||
|
@ -129,12 +129,12 @@ export const enumType = [
|
||||
label: '{{t("is")}}',
|
||||
value: '$eq',
|
||||
selected: true,
|
||||
schema: { 'x-component': 'Select' },
|
||||
schema: { 'x-component': 'Select', 'x-component-props': { mode: null } },
|
||||
},
|
||||
{
|
||||
label: '{{t("is not")}}',
|
||||
value: '$ne',
|
||||
schema: { 'x-component': 'Select' },
|
||||
schema: { 'x-component': 'Select', 'x-component-props': { mode: null } },
|
||||
},
|
||||
{
|
||||
label: '{{t("is any of")}}',
|
||||
|
@ -31,6 +31,12 @@ export class TextareaFieldInterface extends CollectionFieldInterface {
|
||||
titleUsable = true;
|
||||
properties = {
|
||||
...defaultProps,
|
||||
trim: {
|
||||
type: 'boolean',
|
||||
'x-content': '{{t("Automatically remove heading and tailing spaces")}}',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
},
|
||||
};
|
||||
schemaInitialize(schema: ISchema, { block }) {
|
||||
if (['Table', 'Kanban'].includes(block)) {
|
||||
|
@ -7,9 +7,9 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import { filter, unionBy, uniq } from 'lodash';
|
||||
import type { CollectionFieldOptions, GetCollectionFieldPredicate } from '../../data-source';
|
||||
import { Collection } from '../../data-source/collection/Collection';
|
||||
import _, { filter, unionBy, uniq } from 'lodash';
|
||||
|
||||
export class InheritanceCollectionMixin extends Collection {
|
||||
protected parentCollectionsName: string[];
|
||||
@ -22,6 +22,7 @@ export class InheritanceCollectionMixin extends Collection {
|
||||
protected parentCollectionFields: Record<string, CollectionFieldOptions[]> = {};
|
||||
protected allCollectionsInheritChain: string[];
|
||||
protected inheritCollectionsChain: string[];
|
||||
protected inheritChain: string[];
|
||||
protected foreignKeyFields: CollectionFieldOptions[];
|
||||
|
||||
getParentCollectionsName() {
|
||||
@ -233,6 +234,43 @@ export class InheritanceCollectionMixin extends Collection {
|
||||
return this.inheritCollectionsChain;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有祖先数据表和后代数据表,不包括兄弟表。用于下面这些地方:
|
||||
* - 筛选区块链接数据区块时使用
|
||||
*/
|
||||
getInheritChain() {
|
||||
if (this.inheritChain) {
|
||||
return this.inheritChain.slice();
|
||||
}
|
||||
|
||||
const ancestorChain = this.getInheritCollectionsChain();
|
||||
const descendantNames = this.getChildrenCollectionsName();
|
||||
|
||||
// 构建最终的链,首先包含祖先链(包括自身)
|
||||
const inheritChain = [...ancestorChain];
|
||||
|
||||
// 再添加直接后代及其后代,但不包括兄弟表
|
||||
const addDescendants = (names: string[]) => {
|
||||
for (const name of names) {
|
||||
if (!inheritChain.includes(name)) {
|
||||
inheritChain.push(name);
|
||||
const childCollection = this.collectionManager.getCollection<InheritanceCollectionMixin>(name);
|
||||
if (childCollection) {
|
||||
// 递归添加每个后代的后代
|
||||
const childrenNames = childCollection.getChildrenCollectionsName();
|
||||
addDescendants(childrenNames);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 从当前集合的直接后代开始添加
|
||||
addDescendants(descendantNames);
|
||||
|
||||
this.inheritChain = inheritChain;
|
||||
return this.inheritChain;
|
||||
}
|
||||
|
||||
getAllFields(predicate?: GetCollectionFieldPredicate) {
|
||||
if (this.allFields) {
|
||||
return this.allFields.slice();
|
||||
|
@ -0,0 +1,189 @@
|
||||
import { Application } from '@nocobase/client';
|
||||
import { CollectionManager } from '../../../data-source/collection/CollectionManager';
|
||||
import { InheritanceCollectionMixin } from '../InheritanceCollectionMixin';
|
||||
|
||||
describe('InheritanceCollectionMixin', () => {
|
||||
let app: Application;
|
||||
let collectionManager: CollectionManager;
|
||||
|
||||
beforeEach(() => {
|
||||
app = new Application({
|
||||
dataSourceManager: {
|
||||
collectionMixins: [InheritanceCollectionMixin],
|
||||
},
|
||||
});
|
||||
collectionManager = app.getCollectionManager();
|
||||
});
|
||||
|
||||
describe('getInheritChain', () => {
|
||||
it('should return itself when there are no ancestors or descendants', () => {
|
||||
const options = {
|
||||
name: 'test',
|
||||
fields: [{ name: 'field1', interface: 'input' }],
|
||||
};
|
||||
|
||||
collectionManager.addCollections([options]);
|
||||
const collection = collectionManager.getCollection<InheritanceCollectionMixin>('test');
|
||||
|
||||
const inheritChain = collection.getInheritChain();
|
||||
expect(inheritChain).toEqual(['test']);
|
||||
});
|
||||
|
||||
it('should return a chain including all ancestor tables', () => {
|
||||
// 创建三代数据表结构:grandparent -> parent -> child
|
||||
const grandparentOptions = {
|
||||
name: 'grandparent',
|
||||
fields: [{ name: 'field1', interface: 'input' }],
|
||||
};
|
||||
const parentOptions = {
|
||||
name: 'parent',
|
||||
inherits: ['grandparent'],
|
||||
fields: [{ name: 'field2', interface: 'input' }],
|
||||
};
|
||||
const childOptions = {
|
||||
name: 'child',
|
||||
inherits: ['parent'],
|
||||
fields: [{ name: 'field3', interface: 'input' }],
|
||||
};
|
||||
|
||||
// 先将所有集合添加到 collectionManager
|
||||
collectionManager.addCollections([grandparentOptions, parentOptions, childOptions]);
|
||||
|
||||
// 获取最终的集合实例以调用方法
|
||||
const child = collectionManager.getCollection<InheritanceCollectionMixin>('child');
|
||||
|
||||
// 测试 child 的继承链包含所有祖先表
|
||||
const inheritChain = child.getInheritChain();
|
||||
expect(inheritChain).toContain('child');
|
||||
expect(inheritChain).toContain('parent');
|
||||
expect(inheritChain).toContain('grandparent');
|
||||
expect(inheritChain.length).toBe(3);
|
||||
});
|
||||
|
||||
it('should include all descendant tables, but not sibling tables', () => {
|
||||
// 创建具有兄弟和后代关系的数据表结构
|
||||
// parent (祖先表)
|
||||
// |-- child1 (子表)
|
||||
// | |-- grandChild1 (孙表1)
|
||||
// | |-- grandChild2 (孙表2)
|
||||
// |-- child2 (兄弟表)
|
||||
// |-- grandChild3 (兄弟的子表,不应该包括在测试集合的继承链中)
|
||||
|
||||
const collections = [
|
||||
{
|
||||
name: 'parent',
|
||||
fields: [{ name: 'parentField', interface: 'input' }],
|
||||
},
|
||||
{
|
||||
name: 'child1',
|
||||
inherits: ['parent'],
|
||||
fields: [{ name: 'child1Field', interface: 'input' }],
|
||||
},
|
||||
{
|
||||
name: 'child2',
|
||||
inherits: ['parent'],
|
||||
fields: [{ name: 'child2Field', interface: 'input' }],
|
||||
},
|
||||
{
|
||||
name: 'grandChild1',
|
||||
inherits: ['child1'],
|
||||
fields: [{ name: 'grandChild1Field', interface: 'input' }],
|
||||
},
|
||||
{
|
||||
name: 'grandChild2',
|
||||
inherits: ['child1'],
|
||||
fields: [{ name: 'grandChild2Field', interface: 'input' }],
|
||||
},
|
||||
{
|
||||
name: 'grandChild3',
|
||||
inherits: ['child2'],
|
||||
fields: [{ name: 'grandChild3Field', interface: 'input' }],
|
||||
},
|
||||
];
|
||||
|
||||
// 一次性添加所有集合
|
||||
collectionManager.addCollections(collections);
|
||||
|
||||
// 获取要测试的集合实例
|
||||
const child1 = collectionManager.getCollection<InheritanceCollectionMixin>('child1');
|
||||
|
||||
// 测试 child1 的继承链
|
||||
const child1InheritChain = child1.getInheritChain();
|
||||
|
||||
// 应该包含自身、父表和子表
|
||||
expect(child1InheritChain).toContain('child1');
|
||||
expect(child1InheritChain).toContain('parent');
|
||||
expect(child1InheritChain).toContain('grandChild1');
|
||||
expect(child1InheritChain).toContain('grandChild2');
|
||||
|
||||
// 不应该包含兄弟表及其子表
|
||||
expect(child1InheritChain).not.toContain('child2');
|
||||
expect(child1InheritChain).not.toContain('grandChild3');
|
||||
|
||||
// 检查总数量是否正确 (parent, child1, grandChild1, grandChild2)
|
||||
expect(child1InheritChain.length).toBe(4);
|
||||
});
|
||||
|
||||
it('should properly handle multiple inheritance', () => {
|
||||
// 创建多重继承的数据表结构
|
||||
// parent1 parent2
|
||||
// \ /
|
||||
// \ /
|
||||
// child
|
||||
// |
|
||||
// grandChild
|
||||
|
||||
const collections = [
|
||||
{
|
||||
name: 'parent1',
|
||||
fields: [{ name: 'parent1Field', interface: 'input' }],
|
||||
},
|
||||
{
|
||||
name: 'parent2',
|
||||
fields: [{ name: 'parent2Field', interface: 'input' }],
|
||||
},
|
||||
{
|
||||
name: 'child',
|
||||
inherits: ['parent1', 'parent2'],
|
||||
fields: [{ name: 'childField', interface: 'input' }],
|
||||
},
|
||||
{
|
||||
name: 'grandChild',
|
||||
inherits: ['child'],
|
||||
fields: [{ name: 'grandChildField', interface: 'input' }],
|
||||
},
|
||||
];
|
||||
|
||||
// 一次性添加所有集合
|
||||
collectionManager.addCollections(collections);
|
||||
|
||||
// 获取要测试的集合实例
|
||||
const child = collectionManager.getCollection<InheritanceCollectionMixin>('child');
|
||||
const grandChild = collectionManager.getCollection<InheritanceCollectionMixin>('grandChild');
|
||||
|
||||
// 测试 child 的继承链
|
||||
const childInheritChain = child.getInheritChain();
|
||||
|
||||
// 应该包含自身、两个父表和子表
|
||||
expect(childInheritChain).toContain('child');
|
||||
expect(childInheritChain).toContain('parent1');
|
||||
expect(childInheritChain).toContain('parent2');
|
||||
expect(childInheritChain).toContain('grandChild');
|
||||
|
||||
// 检查总数量是否正确 (child, parent1, parent2, grandChild)
|
||||
expect(childInheritChain.length).toBe(4);
|
||||
|
||||
// 测试 grandChild 的继承链
|
||||
const grandChildInheritChain = grandChild.getInheritChain();
|
||||
|
||||
// 应该包含自身及所有祖先表
|
||||
expect(grandChildInheritChain).toContain('grandChild');
|
||||
expect(grandChildInheritChain).toContain('child');
|
||||
expect(grandChildInheritChain).toContain('parent1');
|
||||
expect(grandChildInheritChain).toContain('parent2');
|
||||
|
||||
// 检查总数量是否正确 (grandChild, child, parent1, parent2)
|
||||
expect(grandChildInheritChain.length).toBe(4);
|
||||
});
|
||||
});
|
||||
});
|
30
packages/core/client/src/common/AppNotFound.tsx
Normal file
30
packages/core/client/src/common/AppNotFound.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 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 { Button, Result } from 'antd';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
export const AppNotFound = () => {
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Result
|
||||
status="404"
|
||||
title="404"
|
||||
subTitle={t('Sorry, the page you visited does not exist.')}
|
||||
extra={
|
||||
<Button onClick={() => navigate('/', { replace: true })} type="primary">
|
||||
Back Home
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
23
packages/core/client/src/common/getVariableValue.ts
Normal file
23
packages/core/client/src/common/getVariableValue.ts
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* 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 { evaluators } from '@nocobase/evaluators/client';
|
||||
import { replaceVariables } from '../schema-settings/LinkageRules/bindLinkageRulesToFiled';
|
||||
|
||||
export const getVariableValue = async (text: string, scopes) => {
|
||||
if (!text) {
|
||||
return text;
|
||||
}
|
||||
|
||||
const { evaluate } = evaluators.get('string');
|
||||
|
||||
const { exp, scope: expScope } = await replaceVariables(text, scopes);
|
||||
const result = evaluate(exp, { now: () => new Date().toString(), ...expScope });
|
||||
return result;
|
||||
};
|
@ -7,5 +7,7 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
export * from './AppNotFound';
|
||||
export * from './SelectWithTitle';
|
||||
export * from './useFieldComponentName';
|
||||
export * from './getVariableValue';
|
||||
|
@ -18,6 +18,7 @@ import { useCollectionFieldUISchema, useIsInNocoBaseRecursionFieldContext } from
|
||||
import { useDynamicComponentProps } from '../../hoc/withDynamicSchemaProps';
|
||||
import { useCompile, useComponent } from '../../schema-component';
|
||||
import { useIsAllowToSetDefaultValue } from '../../schema-settings/hooks/useIsAllowToSetDefaultValue';
|
||||
import { isVariable } from '../../variables/utils/isVariable';
|
||||
import { CollectionFieldProvider, useCollectionField } from './CollectionFieldProvider';
|
||||
|
||||
type Props = {
|
||||
@ -104,19 +105,45 @@ const CollectionFieldInternalField = (props) => {
|
||||
const dynamicProps = useDynamicComponentProps(uiSchema?.['x-use-component-props'], props);
|
||||
|
||||
useEffect(() => {
|
||||
// There seems to be a bug in formily where after setting a field to readPretty, switching to editable,
|
||||
// then back to readPretty, and refreshing the page, the field remains in editable state. The expected state is readPretty.
|
||||
// This code is meant to fix this issue.
|
||||
/**
|
||||
* There seems to be a bug in formily where after setting a field to readPretty, switching to editable,
|
||||
* then back to readPretty, and refreshing the page, the field remains in editable state. The expected state is readPretty.
|
||||
* This code is meant to fix this issue.
|
||||
*/
|
||||
if (fieldSchema['x-read-pretty'] === true && !field.readPretty) {
|
||||
field.readPretty = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This solves the issue: After creating a form and setting a field to "read-only", the field remains editable when refreshing the page and reopening the dialog.
|
||||
*
|
||||
* Note: This might be a bug in Formily
|
||||
* When both x-disabled and x-read-pretty exist in the Schema:
|
||||
* - If x-disabled appears before x-read-pretty in the Schema JSON, the disabled state becomes ineffective
|
||||
* - The reason is that during field instance initialization, field.disabled is set before field.readPretty, which causes the pattern value to be changed to 'editable'
|
||||
* - This issue is related to the order of JSON fields, which might return different orders in different environments (databases), thus making the issue inconsistent to reproduce
|
||||
*
|
||||
* Reference to Formily source code:
|
||||
* 1. Setting readPretty may cause pattern to be changed to 'editable': https://github.com/alibaba/formily/blob/d4bb96c40e7918210b1bd7d57b8fadee0cfe4b26/packages/core/src/models/BaseField.ts#L208-L224
|
||||
* 2. The execution order of the each method depends on the order of JSON fields: https://github.com/alibaba/formily/blob/123d536b6076196e00b4e02ee160d72480359f54/packages/json-schema/src/schema.ts#L486-L519
|
||||
*/
|
||||
if (fieldSchema['x-disabled'] === true) {
|
||||
field.disabled = true;
|
||||
}
|
||||
field.data = field.data || {};
|
||||
field.data.dataSource = uiSchema?.enum;
|
||||
}, [field, fieldSchema]);
|
||||
|
||||
if (!uiSchema) return null;
|
||||
|
||||
return <Component {...props} {...dynamicProps} />;
|
||||
const mergedProps = { ...props, ...dynamicProps };
|
||||
|
||||
// Prevent displaying the variable string first, then the variable value
|
||||
if (isVariable(mergedProps.value) && mergedProps.value === fieldSchema.default) {
|
||||
mergedProps.value = undefined;
|
||||
}
|
||||
|
||||
return <Component {...mergedProps} />;
|
||||
};
|
||||
|
||||
export const CollectionField = connect((props) => {
|
||||
|
@ -23,6 +23,7 @@ import {
|
||||
import { CollectionRecord } from '../collection-record';
|
||||
import { BlockRequestProvider } from './DataBlockRequestProvider';
|
||||
import { DataBlockResourceProvider } from './DataBlockResourceProvider';
|
||||
import { BlockLinkageRuleProvider } from '../../modules/blocks/BlockLinkageRuleProvider';
|
||||
|
||||
export interface AllDataBlockProps {
|
||||
collection: string | CollectionOptions;
|
||||
@ -189,6 +190,7 @@ export const DataBlockProvider: FC<Partial<AllDataBlockProps>> = withDynamicSche
|
||||
<CollectionManagerProvider dataSource={dataSource}>
|
||||
<AssociationOrCollectionProvider collection={collection} association={association}>
|
||||
<ACLCollectionProvider>
|
||||
<BlockLinkageRuleProvider>
|
||||
<DataBlockResourceProvider>
|
||||
<BlockRequestProvider>
|
||||
<DataBlockCollector params={props.params}>
|
||||
@ -196,6 +198,7 @@ export const DataBlockProvider: FC<Partial<AllDataBlockProps>> = withDynamicSche
|
||||
</DataBlockCollector>
|
||||
</BlockRequestProvider>
|
||||
</DataBlockResourceProvider>
|
||||
</BlockLinkageRuleProvider>
|
||||
</ACLCollectionProvider>
|
||||
</AssociationOrCollectionProvider>
|
||||
</CollectionManagerProvider>
|
||||
|
@ -29,7 +29,7 @@ export function useDataSourceManager() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前 collection 继承链路上的所有 collection
|
||||
* 获取当前 collection 继承链路上的所有 collection(不包括兄弟表)
|
||||
* @returns
|
||||
*/
|
||||
export function useAllCollectionsInheritChainGetter() {
|
||||
@ -39,7 +39,7 @@ export function useAllCollectionsInheritChainGetter() {
|
||||
return dm
|
||||
?.getDataSource(customDataSource)
|
||||
?.collectionManager?.getCollection<InheritanceCollectionMixin>(collectionName)
|
||||
?.getAllCollectionsInheritChain();
|
||||
?.getInheritChain();
|
||||
},
|
||||
[dm],
|
||||
);
|
||||
|
@ -19,6 +19,7 @@ import { mergeFilter, useAssociatedFields } from './utils';
|
||||
|
||||
// @ts-ignore
|
||||
import React, { createContext, useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { useAllDataBlocks } from '../schema-component/antd/page/AllDataBlocksProvider';
|
||||
|
||||
enum FILTER_OPERATOR {
|
||||
AND = '$and',
|
||||
@ -71,6 +72,10 @@ export interface DataBlock {
|
||||
* manual: 只有当点击了筛选按钮,才会请求数据
|
||||
*/
|
||||
dataLoadingMode?: 'auto' | 'manual';
|
||||
/** 让整个区块悬浮起来 */
|
||||
highlightBlock: () => void;
|
||||
/** 取消悬浮 */
|
||||
unhighlightBlock: () => void;
|
||||
}
|
||||
|
||||
interface FilterContextValue {
|
||||
@ -124,7 +129,7 @@ export const DataBlockCollector = ({
|
||||
const field = useField();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const associatedFields = useAssociatedFields();
|
||||
const container = useRef(null);
|
||||
const container = useRef<HTMLDivElement | null>(null);
|
||||
const dataLoadingMode = useDataLoadingMode();
|
||||
|
||||
const shouldApplyFilter =
|
||||
@ -172,6 +177,34 @@ export const DataBlockCollector = ({
|
||||
field.data?.clearSelectedRowKeys?.();
|
||||
}
|
||||
},
|
||||
highlightBlock() {
|
||||
const dom = container.current;
|
||||
|
||||
if (!dom) return;
|
||||
|
||||
const designer = dom.querySelector('.ant-nb-schema-toolbar');
|
||||
if (designer) {
|
||||
designer.classList.remove(process.env.__E2E__ ? 'hidden-e2e' : 'hidden');
|
||||
}
|
||||
dom.style.boxShadow = '0 3px 12px rgba(0, 0, 0, 0.15)';
|
||||
dom.style.transition = 'box-shadow 0.3s ease, transform 0.2s ease';
|
||||
dom.scrollIntoView?.({
|
||||
behavior: 'smooth',
|
||||
block: 'start',
|
||||
});
|
||||
},
|
||||
unhighlightBlock() {
|
||||
const dom = container.current;
|
||||
|
||||
if (!dom) return;
|
||||
|
||||
const designer = dom.querySelector('.ant-nb-schema-toolbar');
|
||||
if (designer) {
|
||||
designer.classList.add(process.env.__E2E__ ? 'hidden-e2e' : 'hidden');
|
||||
}
|
||||
dom.style.boxShadow = 'none';
|
||||
dom.style.transition = 'box-shadow 0.3s ease, transform 0.2s ease';
|
||||
}
|
||||
});
|
||||
}, [
|
||||
associatedFields,
|
||||
@ -197,12 +230,14 @@ export const DataBlockCollector = ({
|
||||
*/
|
||||
export const useFilterBlock = () => {
|
||||
const ctx = React.useContext(FilterContext);
|
||||
const allDataBlocksCtx = useAllDataBlocks();
|
||||
|
||||
// 有可能存在页面没有提供 FilterBlockProvider 的情况,比如内部使用的数据表管理页面
|
||||
const getDataBlocks = useCallback<() => DataBlock[]>(() => ctx?.getDataBlocks() || [], [ctx]);
|
||||
|
||||
const recordDataBlocks = useCallback(
|
||||
(block: DataBlock) => {
|
||||
allDataBlocksCtx.recordDataBlocks(block);
|
||||
const existingBlock = ctx?.getDataBlocks().find((item) => item.uid === block.uid);
|
||||
|
||||
if (existingBlock) {
|
||||
@ -218,6 +253,7 @@ export const useFilterBlock = () => {
|
||||
|
||||
const removeDataBlock = useCallback(
|
||||
(uid: string) => {
|
||||
allDataBlocksCtx.removeDataBlock(uid);
|
||||
if (ctx?.getDataBlocks().every((item) => item.uid !== uid)) return;
|
||||
ctx?.setDataBlocks((prev) => prev.filter((item) => item.uid !== uid));
|
||||
},
|
||||
|
@ -67,6 +67,108 @@ describe('getSupportFieldsByAssociation', () => {
|
||||
});
|
||||
|
||||
describe('getSupportFieldsByForeignKey', () => {
|
||||
it('should return foreign key fields matching both name and target collection', () => {
|
||||
const filterBlockCollection = {
|
||||
fields: [
|
||||
{ id: 1, name: 'field1', type: 'hasMany', foreignKey: 'fk1', target: 'collection1' },
|
||||
{ id: 2, name: 'field2', type: 'hasMany', foreignKey: 'fk2', target: 'collection2' },
|
||||
{ id: 3, name: 'field3', type: 'hasMany', foreignKey: 'fk3', target: 'collection3' },
|
||||
],
|
||||
};
|
||||
|
||||
const block = {
|
||||
foreignKeyFields: [
|
||||
{ id: 1, name: 'fk1', collectionName: 'collection1' },
|
||||
{ id: 2, name: 'fk2', collectionName: 'collection2' },
|
||||
{ id: 3, name: 'fk3', collectionName: 'collection3' },
|
||||
],
|
||||
};
|
||||
|
||||
const result = getSupportFieldsByForeignKey(filterBlockCollection as any, block as any);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 1, name: 'fk1', collectionName: 'collection1' },
|
||||
{ id: 2, name: 'fk2', collectionName: 'collection2' },
|
||||
{ id: 3, name: 'fk3', collectionName: 'collection3' },
|
||||
]);
|
||||
});
|
||||
|
||||
it("should not return foreign key fields when target collection doesn't match", () => {
|
||||
const filterBlockCollection = {
|
||||
fields: [
|
||||
{ id: 1, name: 'field1', type: 'hasMany', foreignKey: 'fk1', target: 'collection1' },
|
||||
{ id: 2, name: 'field2', type: 'hasMany', foreignKey: 'fk2', target: 'collectionX' }, // target不匹配
|
||||
{ id: 3, name: 'field3', type: 'hasMany', foreignKey: 'fk3', target: 'collection3' },
|
||||
],
|
||||
};
|
||||
|
||||
const block = {
|
||||
foreignKeyFields: [
|
||||
{ id: 1, name: 'fk1', collectionName: 'collection1' },
|
||||
{ id: 2, name: 'fk2', collectionName: 'collection2' }, // 与field2的target不匹配
|
||||
{ id: 3, name: 'fk3', collectionName: 'collection3' },
|
||||
],
|
||||
};
|
||||
|
||||
const result = getSupportFieldsByForeignKey(filterBlockCollection as any, block as any);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 1, name: 'fk1', collectionName: 'collection1' },
|
||||
{ id: 3, name: 'fk3', collectionName: 'collection3' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should filter out belongsTo type fields', () => {
|
||||
const filterBlockCollection = {
|
||||
fields: [
|
||||
{ id: 1, name: 'field1', type: 'hasMany', foreignKey: 'fk1', target: 'collection1' },
|
||||
{ id: 2, name: 'field2', type: 'belongsTo', foreignKey: 'fk2', target: 'collection2' }, // belongsTo类型
|
||||
{ id: 3, name: 'field3', type: 'hasMany', foreignKey: 'fk3', target: 'collection3' },
|
||||
],
|
||||
};
|
||||
|
||||
const block = {
|
||||
foreignKeyFields: [
|
||||
{ id: 1, name: 'fk1', collectionName: 'collection1' },
|
||||
{ id: 2, name: 'fk2', collectionName: 'collection2' },
|
||||
{ id: 3, name: 'fk3', collectionName: 'collection3' },
|
||||
],
|
||||
};
|
||||
|
||||
const result = getSupportFieldsByForeignKey(filterBlockCollection as any, block as any);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 1, name: 'fk1', collectionName: 'collection1' },
|
||||
{ id: 3, name: 'fk3', collectionName: 'collection3' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle when both name and target collection match', () => {
|
||||
const filterBlockCollection = {
|
||||
fields: [
|
||||
{ id: 1, name: 'field1', type: 'hasMany', foreignKey: 'fk1', target: 'collection1' },
|
||||
{ id: 2, name: 'field2', type: 'hasOne', foreignKey: 'fk2', target: 'collection2' },
|
||||
{ id: 3, name: 'field3', type: 'hasMany', foreignKey: 'fk3', target: 'wrongCollection' }, // 目标表不匹配
|
||||
],
|
||||
};
|
||||
|
||||
const block = {
|
||||
foreignKeyFields: [
|
||||
{ id: 1, name: 'fk1', collectionName: 'collection1' },
|
||||
{ id: 2, name: 'fk2', collectionName: 'collection2' },
|
||||
{ id: 3, name: 'fk3', collectionName: 'collection3' }, // 与field3的target不匹配
|
||||
],
|
||||
};
|
||||
|
||||
const result = getSupportFieldsByForeignKey(filterBlockCollection as any, block as any);
|
||||
|
||||
expect(result).toEqual([
|
||||
{ id: 1, name: 'fk1', collectionName: 'collection1' },
|
||||
{ id: 2, name: 'fk2', collectionName: 'collection2' },
|
||||
]);
|
||||
});
|
||||
|
||||
// 保留原有的通用测试用例
|
||||
it("should return all foreign key fields matching the filter block collection's foreign key properties", () => {
|
||||
const filterBlockCollection = {
|
||||
fields: [
|
||||
|
47
packages/core/client/src/filter-provider/highlightBlock.ts
Normal file
47
packages/core/client/src/filter-provider/highlightBlock.ts
Normal file
@ -0,0 +1,47 @@
|
||||
let container: HTMLElement | null = null;
|
||||
|
||||
export const highlightBlock = (clonedBlockDom: HTMLElement, boxRect: DOMRect) => {
|
||||
if (!container) {
|
||||
container = document.createElement('div');
|
||||
document.body.appendChild(container);
|
||||
container.style.position = 'absolute';
|
||||
container.style.transition = 'opacity 0.3s ease';
|
||||
container.style.pointerEvents = 'none';
|
||||
}
|
||||
|
||||
container.appendChild(clonedBlockDom);
|
||||
container.style.opacity = '1';
|
||||
container.style.width = `${boxRect.width}px`;
|
||||
container.style.height = `${boxRect.height}px`;
|
||||
container.style.top = `${boxRect.top}px`;
|
||||
container.style.left = `${boxRect.left}px`;
|
||||
container.style.zIndex = '2000';
|
||||
}
|
||||
|
||||
export const unhighlightBlock = () => {
|
||||
if (container) {
|
||||
container.style.opacity = '0';
|
||||
container.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
export const startScrollEndTracking = (dom: HTMLElement & { _prevRect?: DOMRect; _timer?: any }, callback: () => void) => {
|
||||
dom._timer = setInterval(() => {
|
||||
const prevRect = dom._prevRect;
|
||||
const currentRect = dom.getBoundingClientRect();
|
||||
|
||||
if (!prevRect || currentRect.top !== prevRect.top) {
|
||||
dom._prevRect = currentRect;
|
||||
} else {
|
||||
clearInterval(dom._timer);
|
||||
callback();
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
|
||||
export const stopScrollEndTracking = (dom: HTMLElement & { _timer?: any }) => {
|
||||
if (dom._timer) {
|
||||
clearInterval(dom._timer);
|
||||
dom._timer = null;
|
||||
}
|
||||
}
|
@ -49,10 +49,14 @@ export const getSupportFieldsByAssociation = (inheritCollectionsChain: string[],
|
||||
|
||||
export const getSupportFieldsByForeignKey = (filterBlockCollection: Collection, block: DataBlock) => {
|
||||
return block.foreignKeyFields?.filter((foreignKeyField) => {
|
||||
return filterBlockCollection.fields.some(
|
||||
(field) => field.type !== 'belongsTo' && field.foreignKey === foreignKeyField.name,
|
||||
return filterBlockCollection.fields.some((field) => {
|
||||
return (
|
||||
field.type !== 'belongsTo' &&
|
||||
field.foreignKey === foreignKeyField.name && // 1. 外键字段的 name 要一致
|
||||
field.target === foreignKeyField.collectionName // 2. 关系字段的目标表要和外键的数据表一致
|
||||
);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@ -193,19 +197,21 @@ export const useFilterAPI = () => {
|
||||
|
||||
const doFilter = useCallback(
|
||||
(
|
||||
value: any | ((target: FilterTarget['targets'][0], block: DataBlock) => any),
|
||||
value: any | ((target: FilterTarget['targets'][0], block: DataBlock, sourceKey?: string) => any),
|
||||
field: string | ((target: FilterTarget['targets'][0], block: DataBlock) => string) = 'id',
|
||||
operator: string | ((target: FilterTarget['targets'][0]) => string) = '$eq',
|
||||
) => {
|
||||
const currentBlock = dataBlocks.find((block) => block.uid === fieldSchema.parent['x-uid']);
|
||||
dataBlocks.forEach((block) => {
|
||||
let key = field as string;
|
||||
const target = targets.find((target) => target.uid === block.uid);
|
||||
if (!target) return;
|
||||
|
||||
if (_.isFunction(value)) {
|
||||
value = value(target, block);
|
||||
value = value(target, block, getSourceKey(currentBlock, target.field));
|
||||
}
|
||||
if (_.isFunction(field)) {
|
||||
field = field(target, block);
|
||||
key = field(target, block);
|
||||
}
|
||||
if (_.isFunction(operator)) {
|
||||
operator = operator(target);
|
||||
@ -219,7 +225,7 @@ export const useFilterAPI = () => {
|
||||
storedFilter[uid] = {
|
||||
$and: [
|
||||
{
|
||||
[field]: {
|
||||
[key]: {
|
||||
[operator]: value,
|
||||
},
|
||||
},
|
||||
@ -248,7 +254,7 @@ export const useFilterAPI = () => {
|
||||
);
|
||||
});
|
||||
},
|
||||
[dataBlocks, targets, uid],
|
||||
[dataBlocks, targets, uid, fieldSchema],
|
||||
);
|
||||
|
||||
return {
|
||||
@ -268,3 +274,8 @@ export const isInFilterFormBlock = (fieldSchema: Schema) => {
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
function getSourceKey(currentBlock: DataBlock, field: string) {
|
||||
const associationField = currentBlock?.associatedFields?.find((item) => item.foreignKey === field);
|
||||
return associationField?.sourceKey || field?.split?.('.')?.[1];
|
||||
}
|
||||
|
@ -49,6 +49,8 @@ export interface CustomToken extends AliasToken {
|
||||
marginBlock: number;
|
||||
/** 区块的圆角 */
|
||||
borderRadiusBlock: number;
|
||||
|
||||
siderWidth: number;
|
||||
}
|
||||
|
||||
export interface ThemeConfig extends _ThemeConfig {
|
||||
|
12
packages/core/client/src/i18n/constant.ts
Normal file
12
packages/core/client/src/i18n/constant.ts
Normal file
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
const NAMESPACE_UI_SCHEMA = 'ui-schema-storage';
|
||||
|
||||
export { NAMESPACE_UI_SCHEMA };
|
@ -8,3 +8,4 @@
|
||||
*/
|
||||
|
||||
export * from './i18n';
|
||||
export * from './constant';
|
||||
|
@ -500,7 +500,8 @@
|
||||
"Turn pages": "Seiten umblättern",
|
||||
"Others": "Andere",
|
||||
"Other records": "Andere Datensätze",
|
||||
"Save as template": "Als Vorlage speichern",
|
||||
"Save as reference template": "Als Referenzvorlage speichern",
|
||||
"Save as inherited template": "Als vererbte Vorlage speichern",
|
||||
"Save as block template": "Als Blockvorlage speichern",
|
||||
"Block templates": "Blockvorlagen",
|
||||
"Block template": "Blockvorlage",
|
||||
@ -589,6 +590,7 @@
|
||||
"Blank block": "Leerer Block",
|
||||
"Duplicate template": "Vorlage duplizieren",
|
||||
"Reference template": "Referenzvorlage",
|
||||
"Inherited template": "Vererbte Vorlage",
|
||||
"Create calendar block": "Kalenderblock erstellen",
|
||||
"Create kanban block": "Kanban-Block erstellen",
|
||||
"Grouping field": "Gruppierungsfeld",
|
||||
@ -884,5 +886,10 @@
|
||||
"If selected, the page will display Tab pages.": "Wenn ausgewählt, zeigt die Seite Tab-Seiten an.",
|
||||
"If selected, the route will be displayed in the menu.": "Wenn ausgewählt, wird die Route im Menü angezeigt.",
|
||||
"Are you sure you want to hide this tab?": "Sind Sie sicher, dass Sie diesen Tab ausblenden möchten?",
|
||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Nach dem Ausblenden wird dieser Tab nicht mehr in der Tableiste angezeigt. Um ihn wieder anzuzeigen, müssen Sie zur Routenverwaltungsseite gehen, um ihn einzustellen."
|
||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Nach dem Ausblenden wird dieser Tab nicht mehr in der Tableiste angezeigt. Um ihn wieder anzuzeigen, müssen Sie zur Routenverwaltungsseite gehen, um ihn einzustellen.",
|
||||
"No pages yet, please configure first": "Noch keine Seiten, bitte zuerst konfigurieren",
|
||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Klicken Sie auf das \"UI-Editor\"-Symbol in der oberen rechten Ecke, um den UI-Editor-Modus zu betreten",
|
||||
"Refresh data blocks": "Aktualisieren Sie die Datenblöcke",
|
||||
"Select data blocks to refresh": "Wählen Sie die Datenblöcke aus, die aktualisiert werden sollen.",
|
||||
"After successful submission, the selected data blocks will be automatically refreshed.": "Nach erfolgreicher Übermittlung werden die ausgewählten Datenblöcke automatisch aktualisiert."
|
||||
}
|
||||
|
@ -505,7 +505,7 @@
|
||||
"Save as block template": "Save as block template",
|
||||
"Block templates": "Block templates",
|
||||
"Block template": "Block template",
|
||||
"Convert reference to duplicate": "Convert reference to duplicate",
|
||||
"Convert template to duplicate": "Convert template to duplicate",
|
||||
"Template name": "Template name",
|
||||
"Block type": "Block type",
|
||||
"No blocks to connect": "No blocks to connect",
|
||||
@ -590,6 +590,7 @@
|
||||
"Blank block": "Blank block",
|
||||
"Duplicate template": "Duplicate template",
|
||||
"Reference template": "Reference template",
|
||||
"Inherited template": "Inherited template",
|
||||
"Create calendar block": "Create calendar block",
|
||||
"Create kanban block": "Create kanban block",
|
||||
"Grouping field": "Grouping field",
|
||||
@ -886,7 +887,12 @@
|
||||
"If selected, the route will be displayed in the menu.": "If selected, the route will be displayed in the menu.",
|
||||
"Are you sure you want to hide this tab?": "Are you sure you want to hide this tab?",
|
||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.",
|
||||
"No pages yet, please configure first": "No pages yet, please configure first",
|
||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode",
|
||||
"Deprecated": "Deprecated",
|
||||
"The following old template features have been deprecated and will be removed in next version.": "The following old template features have been deprecated and will be removed in next version.",
|
||||
"Full permissions": "Full permissions"
|
||||
"Full permissions": "Full permissions",
|
||||
"Refresh data blocks": "Refresh data blocks",
|
||||
"Select data blocks to refresh": "Select data blocks to refresh",
|
||||
"After successful submission, the selected data blocks will be automatically refreshed.": "After successful submission, the selected data blocks will be automatically refreshed."
|
||||
|
||||
}
|
||||
|
@ -470,7 +470,8 @@
|
||||
"Turn pages": "Pasar páginas",
|
||||
"Others": "Otros",
|
||||
"Other records": "Otros registros",
|
||||
"Save as template": "Guardar como plantilla",
|
||||
"Save as reference template": "Guardar como plantilla de referencia",
|
||||
"Save as inherited template": "Guardar como plantilla heredada",
|
||||
"Save as block template": "Guardar como plantilla de bloque",
|
||||
"Block templates": "Bloquear plantillas",
|
||||
"Block template": "Plantilla de bloque",
|
||||
@ -560,6 +561,7 @@
|
||||
"Blank block": "Bloque en blanco",
|
||||
"Duplicate template": "Duplicar plantilla",
|
||||
"Reference template": "Plantilla de referencia",
|
||||
"Inherited template": "Plantilla heredada",
|
||||
"Create calendar block": "Crear bloque de calendario",
|
||||
"Create kanban block": "Crear bloque kanban",
|
||||
"Grouping field": "Campo de agrupación",
|
||||
@ -803,7 +805,11 @@
|
||||
"If selected, the route will be displayed in the menu.": "Si se selecciona, la ruta se mostrará en el menú.",
|
||||
"Are you sure you want to hide this tab?": "¿Estás seguro de que quieres ocultar esta pestaña?",
|
||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Después de ocultar, esta pestaña ya no aparecerá en la barra de pestañas. Para mostrarla de nuevo, deberás ir a la página de gestión de rutas para configurarla.",
|
||||
"No pages yet, please configure first": "Aún no hay páginas, por favor configura primero",
|
||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Haga clic en el icono \"Editor de UI\" en la esquina superior derecha para entrar en el modo de Editor de UI.",
|
||||
"Deprecated": "Obsoleto",
|
||||
"The following old template features have been deprecated and will be removed in next version.": "Las siguientes características de plantilla antigua han quedado obsoletas y se eliminarán en la próxima versión.",
|
||||
"Full permissions": "Todos los derechos"
|
||||
"Full permissions": "Todos los derechos",
|
||||
"Refresh data blocks": "Actualizar bloques de datos",
|
||||
"Select data blocks to refresh": "Actualizar bloques de datos",
|
||||
"After successful submission, the selected data blocks will be automatically refreshed.": "Después de enviar correctamente, los bloques de datos seleccionados se actualizarán automáticamente."
|
||||
}
|
||||
|
@ -485,7 +485,8 @@
|
||||
"Turn pages": "Tourner les pages",
|
||||
"Others": "Autres",
|
||||
"Other records": "Autres enregistrements",
|
||||
"Save as template": "Enregistrer en tant que modèle",
|
||||
"Save as reference template": "Enregistrer en tant que modèle de référence",
|
||||
"Save as inherited template": "Enregistrer en tant que modèle hérité",
|
||||
"Save as block template": "Enregistrer en tant que modèle de bloc",
|
||||
"Block templates": "Modèles de bloc",
|
||||
"Block template": "Modèle de bloc",
|
||||
@ -573,6 +574,7 @@
|
||||
"Blank block": "Bloc vierge",
|
||||
"Duplicate template": "Dupliquer le modèle",
|
||||
"Reference template": "Référencer le modèle",
|
||||
"Inherited template": "Modèle hérité",
|
||||
"Create calendar block": "Créer un bloc de calendrier",
|
||||
"Create kanban block": "Créer un bloc kanban",
|
||||
"Grouping field": "Champ de regroupement",
|
||||
@ -823,7 +825,11 @@
|
||||
"If selected, the route will be displayed in the menu.": "Si sélectionné, la route sera affichée dans le menu.",
|
||||
"Are you sure you want to hide this tab?": "Êtes-vous sûr de vouloir masquer cet onglet ?",
|
||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Après avoir masqué, cette tab ne sera plus affichée dans la barre de tab. Pour la montrer à nouveau, vous devez vous rendre sur la page de gestion des routes pour la configurer.",
|
||||
"No pages yet, please configure first": "Pas encore de pages, veuillez configurer d'abord",
|
||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Cliquez sur l'icône \"Éditeur d'interface utilisateur\" dans le coin supérieur droit pour entrer en mode Éditeur d'interface utilisateur",
|
||||
"Deprecated": "Déprécié",
|
||||
"The following old template features have been deprecated and will be removed in next version.": "Les fonctionnalités des anciens modèles ont été dépréciées et seront supprimées dans la prochaine version.",
|
||||
"Full permissions": "Tous les droits"
|
||||
"Full permissions": "Tous les droits",
|
||||
"Refresh data blocks": "Actualiser les blocs de données",
|
||||
"Select data blocks to refresh": "Actualiser les blocs de données",
|
||||
"After successful submission, the selected data blocks will be automatically refreshed.": "Après une soumission réussie, les blocs de données sélectionnés seront automatiquement actualisés."
|
||||
}
|
||||
|
@ -494,7 +494,8 @@
|
||||
"Turn pages": "Volta pagine",
|
||||
"Others": "Altri",
|
||||
"Other records": "Altri record",
|
||||
"Save as template": "Salva come modello",
|
||||
"Save as reference template": "Salva come modello di riferimento",
|
||||
"Save as inherited template": "Salva come modello ereditato",
|
||||
"Save as block template": "Salva come modello blocco",
|
||||
"Block templates": "Modelli blocco",
|
||||
"Block template": "Modello blocco",
|
||||
@ -580,6 +581,7 @@
|
||||
"Blank block": "Blocco vuoto",
|
||||
"Duplicate template": "Modello duplicato",
|
||||
"Reference template": "Modello di riferimento",
|
||||
"Inherited template": "Modello ereditato",
|
||||
"Create calendar block": "Crea blocco calendario",
|
||||
"Create kanban block": "Crea blocco kanban",
|
||||
"Grouping field": "Campo di raggruppamento",
|
||||
@ -1080,5 +1082,10 @@
|
||||
"If selected, the page will display Tab pages.": "Se selezionato, la pagina visualizzerà le pagine schede.",
|
||||
"If selected, the route will be displayed in the menu.": "Se selezionato, il percorso verrà visualizzato nel menu.",
|
||||
"Are you sure you want to hide this tab?": "Sei sicuro di voler nascondere questa scheda?",
|
||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Dopo averla nascosta, questa scheda non apparirà più nella barra delle schede. Per mostrarla di nuovo, devi andare alla pagina di gestione dei percorsi per configurarlo."
|
||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Dopo averla nascosta, questa scheda non apparirà più nella barra delle schede. Per mostrarla di nuovo, devi andare alla pagina di gestione dei percorsi per configurarlo.",
|
||||
"No pages yet, please configure first": "Nessuna pagina ancora, si prega di configurare prima",
|
||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Cliquez sur l'icône \"Éditeur d'interface utilisateur\" dans le coin supérieur droit pour entrer en mode Éditeur d'interface utilisateur",
|
||||
"Refresh data blocks": "Aggiorna blocchi di dati",
|
||||
"Select data blocks to refresh": "Aggiorna blocchi di dati",
|
||||
"After successful submission, the selected data blocks will be automatically refreshed.": "Dopo una soumission réussie, les blocs de données sélectionnés seront automatiquement actualisés."
|
||||
}
|
||||
|
@ -397,7 +397,8 @@
|
||||
"Turn pages": "ページをめくる",
|
||||
"Others": "その他",
|
||||
"Other records": "他のレコード",
|
||||
"Save as template": "テンプレートとして保存",
|
||||
"Save as reference template": "参照テンプレートとして保存",
|
||||
"Save as inherited template": "継承テンプレートとして保存",
|
||||
"Save as block template": "ブロックテンプレートとして保存",
|
||||
"Block templates": "ブロックテンプレート",
|
||||
"Block template": "ブロックテンプレート",
|
||||
@ -472,6 +473,7 @@
|
||||
"Blank block": "空のブロック",
|
||||
"Duplicate template": "テンプレートをコピー",
|
||||
"Reference template": "テンプレートを参照",
|
||||
"Inherited template": "継承テンプレート",
|
||||
"Create calendar block": "カレンダーブロックの作成",
|
||||
"Create kanban block": "かんばんブロックの作成",
|
||||
"Grouping field": "グループフィールド",
|
||||
@ -1041,7 +1043,11 @@
|
||||
"If selected, the route will be displayed in the menu.": "選択されている場合、ルートはメニューに表示されます。",
|
||||
"Are you sure you want to hide this tab?": "このタブを非表示にしますか?",
|
||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "非表示にすると、このタブはタブバーに表示されなくなります。再表示するには、ルート管理ページで設定する必要があります。",
|
||||
"No pages yet, please configure first": "まだページがありません。最初に設定してください",
|
||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "ユーザーインターフェースエディターモードに入るには、右上隅の「UIエディタ」アイコンをクリックしてください",
|
||||
"Deprecated": "非推奨",
|
||||
"The following old template features have been deprecated and will be removed in next version.": "次の古いテンプレート機能は非推奨になり、次のバージョンで削除されます。",
|
||||
"Full permissions": "すべての権限"
|
||||
"Full permissions": "すべての権限",
|
||||
"Refresh data blocks": "データブロックを更新",
|
||||
"Select data blocks to refresh": "データブロックを選択して更新",
|
||||
"After successful submission, the selected data blocks will be automatically refreshed.": "送信後、選択したデータブロックが自動的に更新されます。"
|
||||
}
|
||||
|
@ -517,7 +517,8 @@
|
||||
"Turn pages": "페이지 넘김",
|
||||
"Others": "기타",
|
||||
"Other records": "기타 레코드",
|
||||
"Save as template": "템플릿으로 저장",
|
||||
"Save as reference template": "참조 템플릿으로 저장",
|
||||
"Save as inherited template": "상속 템플릿으로 저장",
|
||||
"Save as block template": "블록 템플릿으로 저장",
|
||||
"Block templates": "블록 템플릿",
|
||||
"Block template": "블록 템플릿",
|
||||
@ -601,6 +602,7 @@
|
||||
"Blank block": "빈 블록",
|
||||
"Duplicate template": "템플릿 복제",
|
||||
"Reference template": "참조 템플릿",
|
||||
"Inherited template": "상속 템플릿",
|
||||
"Create calendar block": "캘린더 블록 생성",
|
||||
"Create kanban block": "칸반 블록 생성",
|
||||
"Grouping field": "그루핑 필드",
|
||||
@ -914,7 +916,11 @@
|
||||
"If selected, the route will be displayed in the menu.": "선택되면 라우트는 메뉴에 표시됩니다.",
|
||||
"Are you sure you want to hide this tab?": "이 탭을 숨기시겠습니까?",
|
||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "숨기면 이 탭은 탭 바에 더 이상 표시되지 않습니다. 다시 표시하려면 라우트 관리 페이지에서 설정해야 합니다.",
|
||||
"No pages yet, please configure first": "아직 페이지가 없습니다. 먼저 설정하십시오",
|
||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "사용자 인터페이스 편집기 모드에 들어가려면 오른쪽 상단의 \"UI 편집기\" 아이콘을 클릭하십시오",
|
||||
"Deprecated": "사용 중단됨",
|
||||
"The following old template features have been deprecated and will be removed in next version.": "다음 오래된 템플릿 기능은 사용 중단되었으며 다음 버전에서 제거될 것입니다.",
|
||||
"Full permissions": "모든 권한"
|
||||
"Full permissions": "모든 권한",
|
||||
"Refresh data blocks": "데이터 블록 새로 고침",
|
||||
"Select data blocks to refresh": "데이터 블록을 선택하여 새로 고침",
|
||||
"After successful submission, the selected data blocks will be automatically refreshed.": "전송 후, 선택한 데이터 블록이 자동으로 새로 고쳐집니다."
|
||||
}
|
||||
|
@ -504,7 +504,8 @@
|
||||
"Turn pages": "Pagina's omslaan",
|
||||
"Others": "Overigen",
|
||||
"Other records": "Andere records",
|
||||
"Save as template": "Opslaan als sjabloon",
|
||||
"Save as reference template": "Opslaan als referentiesjabloon",
|
||||
"Save as inherited template": "Opslaan als overerfde sjabloon",
|
||||
"Save as block template": "Opslaan als bloksjabloon",
|
||||
"Block templates": "Bloksjablonen",
|
||||
"Block template": "Bloksjabloon",
|
||||
@ -593,6 +594,7 @@
|
||||
"Blank block": "Leeg blok",
|
||||
"Duplicate template": "Sjabloon dupliceren",
|
||||
"Reference template": "Sjabloon refereren",
|
||||
"Inherited template": "Overerfde sjabloon",
|
||||
"Create calendar block": "Kalenderblok maken",
|
||||
"Create kanban block": "Kanbanblok maken",
|
||||
"Grouping field": "Groepeer veld",
|
||||
@ -1054,5 +1056,8 @@
|
||||
"Font Size(px)": "Lettergrootte(px)",
|
||||
"Font Weight": "Letterdikte",
|
||||
"Font Style": "Letterstijl",
|
||||
"Italic": "Cursief"
|
||||
"Italic": "Cursief",
|
||||
"Refresh data blocks": "Vernieuw gegevensblokken",
|
||||
"Select data blocks to refresh": "Selecteer gegevensblokken om te vernieuwen",
|
||||
"After successful submission, the selected data blocks will be automatically refreshed.": "Na succesvolle indiening worden de geselecteerde gegevensblokken automatisch vernieuwd."
|
||||
}
|
@ -433,7 +433,8 @@
|
||||
"Turn pages": "Virar páginas",
|
||||
"Others": "Outros",
|
||||
"Other records": "Outros registros",
|
||||
"Save as template": "Salvar como modelo",
|
||||
"Save as reference template": "Salvar como modelo de referência",
|
||||
"Save as inherited template": "Salvar como modelo herdado",
|
||||
"Save as block template": "Salvar como modelo de bloco",
|
||||
"Block templates": "Modelos de bloco",
|
||||
"Block template": "Modelo de bloco",
|
||||
@ -525,6 +526,7 @@
|
||||
"Blank block": "Bloco em branco",
|
||||
"Duplicate template": "Duplicar modelo",
|
||||
"Reference template": "Modelo de referência",
|
||||
"Inherited template": "Modelo herdado",
|
||||
"Create calendar block": "Criar bloco de calendário",
|
||||
"Create kanban block": "Criar bloco Kanban",
|
||||
"Grouping field": "Campo de agrupamento",
|
||||
@ -781,6 +783,10 @@
|
||||
"Are you sure you want to hide this tab?": "Tem certeza de que deseja ocultar esta guia?",
|
||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Depois de ocultar, esta guia não aparecerá mais na barra de guias. Para mostrá-la novamente, você precisa ir à página de gerenciamento de rotas para configurá-la.",
|
||||
"Deprecated": "Descontinuado",
|
||||
"The following old template features have been deprecated and will be removed in next version.": "As seguintes funcionalidades de modelo antigo foram descontinuadas e serão removidas na próxima versão.",
|
||||
"Full permissions": "Todas as permissões"
|
||||
"Full permissions": "Todas as permissões",
|
||||
"No pages yet, please configure first": "Ainda não há páginas, por favor configure primeiro",
|
||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Cliquez sur l'icône \"Éditeur d'interface utilisateur\" dans le coin supérieur droit pour entrer en mode Éditeur d'interface utilisateur",
|
||||
"Refresh data blocks": "Atualizar blocos de dados",
|
||||
"Select data blocks to refresh": "Selecionar blocos de dados para atualizar",
|
||||
"After successful submission, the selected data blocks will be automatically refreshed.": "Após a atualização em massa bem sucedida."
|
||||
}
|
||||
|
@ -337,7 +337,8 @@
|
||||
"Turn pages": "Перелистывать страницы",
|
||||
"Others": "Другие",
|
||||
"Other records": "Другие записи",
|
||||
"Save as template": "Сохранить как шаблон",
|
||||
"Save as reference template": "Сохранить как шаблон ссылки",
|
||||
"Save as inherited template": "Сохранить как шаблон наследования",
|
||||
"Save as block template": "Сохранить как шаблон Блока",
|
||||
"Block templates": "Шаблоны блоков",
|
||||
"Convert reference to duplicate": "Преобразовать ссылку в дубликат",
|
||||
@ -411,6 +412,7 @@
|
||||
"Blank block": "Пустой блок",
|
||||
"Duplicate template": "Дублировать шаблон",
|
||||
"Reference template": "Справочный шаблон",
|
||||
"Inherited template": "Наследуемый шаблон",
|
||||
"Create calendar block": "Создать блок календаря",
|
||||
"Create kanban block": "Создать блок Канбан",
|
||||
"Grouping field": "Поле группировки",
|
||||
@ -610,6 +612,10 @@
|
||||
"Are you sure you want to hide this tab?": "Вы уверены, что хотите скрыть эту вкладку?",
|
||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "После скрытия этой вкладки она больше не будет отображаться во вкладке. Чтобы снова отобразить ее, вам нужно будет перейти на страницу управления маршрутами, чтобы установить ее.",
|
||||
"Deprecated": "Устаревший",
|
||||
"The following old template features have been deprecated and will be removed in next version.": "Следующие старые функции шаблонов устарели и будут удалены в следующей версии.",
|
||||
"Full permissions": "Полные права"
|
||||
"Full permissions": "Полные права",
|
||||
"No pages yet, please configure first": "Пока нет страниц, пожалуйста, настройте сначала",
|
||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Нажмите на значок \"Редактор пользовательского интерфейса\" в правом верхнем углу, чтобы войти в режим редактора пользовательского интерфейса",
|
||||
"Refresh data blocks": "Обновить блоки данных",
|
||||
"Select data blocks to refresh": "Выберите блоки данных для обновления",
|
||||
"After successful submission, the selected data blocks will be automatically refreshed.": "После успешной отправки выбранные блоки данных будут автоматически обновлены."
|
||||
}
|
||||
|
@ -336,7 +336,8 @@
|
||||
"Turn pages": "Sayfaları çevir",
|
||||
"Others": "Diğerleri",
|
||||
"Other records": "Diğer kayıtlar",
|
||||
"Save as template": "Şablon olarak kaydet",
|
||||
"Save as reference template": "Referans şablonu olarak kaydet",
|
||||
"Save as inherited template": "Kalıtım şablonu olarak kaydet",
|
||||
"Save as block template": "Blok şablonu olarak kaydet",
|
||||
"Block templates": "Blok şablonları",
|
||||
"Block template": "Blok şablonu",
|
||||
@ -411,6 +412,7 @@
|
||||
"Blank block": "Boş blok",
|
||||
"Duplicate template": "Şablonun kopyasını oluştur",
|
||||
"Reference template": "Referans şablon",
|
||||
"Inherited template": "Kalıtım şablonu",
|
||||
"Create calendar block": "Takvim bloğu oluştur",
|
||||
"Create kanban block": "Kanban bloğu oluştur",
|
||||
"Grouping field": "Alan gruplandırma",
|
||||
@ -608,6 +610,10 @@
|
||||
"Are you sure you want to hide this tab?": "Bu sekmeyi gizlemek istediğinizden emin misiniz?",
|
||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Gizlendikten sonra, bu sekme artık sekme çubuğunda görünmeyecek. Onu tekrar göstermek için, rotayı yönetim sayfasına gidip ayarlamanız gerekiyor.",
|
||||
"Deprecated": "Kullanımdan kaldırıldı",
|
||||
"The following old template features have been deprecated and will be removed in next version.": "Aşağıdaki eski şablon özellikleri kullanımdan kaldırıldı ve gelecek sürümlerde kaldırılacaktır.",
|
||||
"Full permissions": "Tüm izinler"
|
||||
"Full permissions": "Tüm izinler",
|
||||
"No pages yet, please configure first": "Henüz sayfa yok, lütfen önce yapılandırın",
|
||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Kullanıcı arayüzü düzenleyici moduna girmek için sağ üst köşedeki \"Kullanıcı Arayüzü Düzenleyici\" simgesine tıklayın",
|
||||
"Refresh data blocks": "Yenile veri blokları",
|
||||
"Select data blocks to refresh": "Veri bloklarını yenilemek için seçin",
|
||||
"After successful submission, the selected data blocks will be automatically refreshed.": "Başarılı bir şekilde gönderildikten sonra, seçilen veri blokları otomatik olarak yenilenecektir."
|
||||
}
|
||||
|
@ -487,7 +487,8 @@
|
||||
"Turn pages": "Переключати сторінки",
|
||||
"Others": "Інші",
|
||||
"Other records": "Інші записи",
|
||||
"Save as template": "Зберегти як шаблон",
|
||||
"Save as reference template": "Зберегти як шаблон посилання",
|
||||
"Save as inherited template": "Зберегти як шаблон нащадка",
|
||||
"Save as block template": "Зберегти як шаблон блока",
|
||||
"Block templates": "Шаблони блоків",
|
||||
"Block template": "Шаблон блока",
|
||||
@ -577,6 +578,7 @@
|
||||
"Blank block": "Порожній блок",
|
||||
"Duplicate template": "Дублювати шаблон",
|
||||
"Reference template": "Посилання на шаблон",
|
||||
"Inherited template": "Нащадковий шаблон",
|
||||
"Create calendar block": "Створити блок календаря",
|
||||
"Create kanban block": "Створити блок канбану",
|
||||
"Grouping field": "Поле для групування",
|
||||
@ -824,6 +826,10 @@
|
||||
"Are you sure you want to hide this tab?": "Ви впевнені, що хочете приховати цю вкладку?",
|
||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "Після приховування цієї вкладки вона більше не з'явиться в панелі вкладок. Щоб знову показати її, вам потрібно перейти на сторінку керування маршрутами, щоб налаштувати її.",
|
||||
"Deprecated": "Застаріло",
|
||||
"The following old template features have been deprecated and will be removed in next version.": "Наступні старі функції шаблонів були застарілі і будуть видалені в наступній версії.",
|
||||
"Full permissions": "Повні права"
|
||||
"Full permissions": "Повні права",
|
||||
"No pages yet, please configure first": "Ще немає сторінок, будь ласка, спочатку налаштуйте",
|
||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "Натисніть на значок \"Редактор користувацького інтерфейсу\" в правому верхньому куті, щоб увійти в режим редактора користувацького інтерфейсу.",
|
||||
"Refresh data blocks": "Оновити дані блоків",
|
||||
"Select data blocks to refresh": "Виберіть блоки даних для оновлення",
|
||||
"After successful submission, the selected data blocks will be automatically refreshed.": "Після успішної подачі вибрані блоки даних будуть автоматично оновлені."
|
||||
}
|
@ -167,6 +167,8 @@
|
||||
"Year": "年",
|
||||
"QuarterYear": "季度",
|
||||
"Select grouping field": "选择分组字段",
|
||||
"Refresh data blocks": "刷新数据区块",
|
||||
"Select data blocks to refresh": "选择要刷新的数据区块",
|
||||
"Media": "多媒体",
|
||||
"Markdown": "Markdown",
|
||||
"Wysiwyg": "富文本",
|
||||
@ -259,6 +261,7 @@
|
||||
"Parent collection fields": "父表字段",
|
||||
"Basic": "基本类型",
|
||||
"Single line text": "单行文本",
|
||||
"Automatically remove heading and tailing spaces": "自动去除首尾空白字符",
|
||||
"Long text": "多行文本",
|
||||
"Phone": "手机号码",
|
||||
"Email": "电子邮箱",
|
||||
@ -524,7 +527,8 @@
|
||||
"Turn pages": "翻页",
|
||||
"Others": "其他",
|
||||
"Other records": "其他记录",
|
||||
"Save as template": "保存为模板",
|
||||
"Save as reference template": "保存为引用模板",
|
||||
"Save as inherited template": "保存为继承模板",
|
||||
"Save as block template": "保存为区块模板",
|
||||
"Block templates": "区块模板",
|
||||
"Block template": "区块模板",
|
||||
@ -608,6 +612,7 @@
|
||||
"Blank block": "空区块",
|
||||
"Duplicate template": "复制模板",
|
||||
"Reference template": "引用模板",
|
||||
"Inherited template": "继承模板",
|
||||
"Create calendar block": "创建日历区块",
|
||||
"Create kanban block": "创建看板区块",
|
||||
"Grouping field": "分组字段",
|
||||
@ -818,7 +823,8 @@
|
||||
"File size should not exceed {{size}}.": "文件大小不能超过 {{size}}",
|
||||
"File size exceeds the limit": "文件大小超过限制",
|
||||
"File type is not allowed": "文件类型不允许",
|
||||
"Incomplete uploading files need to be resolved": "未完成上传的文件需要处理",
|
||||
"Uploading": "上传中",
|
||||
"Some files are not uploaded correctly, please check.": "部分文件未上传成功,请检查。",
|
||||
"Default title for each record": "用作数据的默认标题",
|
||||
"If collection inherits, choose inherited collections as templates": "当前表有继承关系时,可选择继承链路上的表作为模板来源",
|
||||
"Select an existing piece of data as the initialization data for the form": "选择一条已有的数据作为表单的初始化数据",
|
||||
@ -1084,7 +1090,6 @@
|
||||
"Are you sure you want to hide this tab?": "你确定要隐藏该标签页吗?",
|
||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "隐藏后,该标签将不再显示在标签栏中。要想再次显示它,你需要到路由管理页面进行设置。",
|
||||
"Deprecated": "已弃用",
|
||||
"The following old template features have been deprecated and will be removed in next version.": "以下旧的模板功能已弃用,将在下个版本移除。",
|
||||
"Full permissions": "全部权限",
|
||||
"Enable index column": "启用序号列",
|
||||
"Date scope": "日期范围",
|
||||
@ -1095,5 +1100,11 @@
|
||||
"Font Weight": "字体粗细",
|
||||
"Font Style": "字体样式",
|
||||
"Italic": "斜体",
|
||||
"Response record":"响应结果记录"
|
||||
"Response record":"响应结果记录",
|
||||
"Colon":"冒号",
|
||||
"No pages yet, please configure first": "暂无页面,请先配置",
|
||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "点击右上角的“界面配置”图标,进入界面配置模式",
|
||||
"After successful submission, the selected data blocks will be automatically refreshed.": "提交成功后,会自动刷新这里选中的数据区块。",
|
||||
"Block Linkage rules":"区块联动规则",
|
||||
"Field Linkage rules":"字段联动规则"
|
||||
}
|
||||
|
@ -518,7 +518,8 @@
|
||||
"Turn pages": "翻頁",
|
||||
"Others": "其他",
|
||||
"Other records": "其他記錄",
|
||||
"Save as template": "儲存為模板",
|
||||
"Save as reference template": "儲存為引用模板",
|
||||
"Save as inherited template": "儲存為繼承模板",
|
||||
"Save as block template": "儲存為區塊模板",
|
||||
"Block templates": "區塊模板",
|
||||
"Block template": "區塊模板",
|
||||
@ -602,6 +603,7 @@
|
||||
"Blank block": "空區塊",
|
||||
"Duplicate template": "複製模板",
|
||||
"Reference template": "引用模板",
|
||||
"Inherited template": "繼承模板",
|
||||
"Create calendar block": "建立日曆區塊",
|
||||
"Create kanban block": "建立看板區塊",
|
||||
"Grouping field": "群組欄位",
|
||||
@ -915,6 +917,10 @@
|
||||
"Are you sure you want to hide this tab?": "你確定要隱藏這個標籤嗎?",
|
||||
"After hiding, this tab will no longer appear in the tab bar. To show it again, you need to go to the route management page to set it.": "隱藏後,這個標籤將不再出現在標籤欄中。要再次顯示它,你需要到路由管理頁面進行設置。",
|
||||
"Deprecated": "已棄用",
|
||||
"The following old template features have been deprecated and will be removed in next version.": "以下舊的模板功能已棄用,將在下個版本移除。",
|
||||
"Full permissions": "完全權限"
|
||||
"Full permissions": "完全權限",
|
||||
"No pages yet, please configure first": "尚未配置頁面,請先配置",
|
||||
"Click the \"UI Editor\" icon in the upper right corner to enter the UI Editor mode": "點擊右上角的 \"介面設定\" 圖示進入介面設定模式",
|
||||
"Refresh data blocks": "刷新數據區塊",
|
||||
"Select data blocks to refresh": "選擇要刷新的數據區塊",
|
||||
"After successful submission, the selected data blocks will be automatically refreshed.": "提交成功後,選中的數據區塊將自動刷新。"
|
||||
}
|
@ -31,10 +31,15 @@ test.describe('Link', () => {
|
||||
await expect(
|
||||
page.getByRole('button', { name: 'designer-schema-settings-Action.Link-actionSettings:link-users' }),
|
||||
).toHaveCount(1);
|
||||
await page.getByRole('button', { name: 'designer-schema-settings-Action.Link-actionSettings:link-users' }).hover();
|
||||
await page.getByRole('menuitem', { name: 'Edit link' }).click();
|
||||
await page
|
||||
.getByLabel('block-item-users-table-URL')
|
||||
.getByRole('button', { name: 'designer-schema-settings-Action.Link-actionSettings:link-users' })
|
||||
.first()
|
||||
.hover();
|
||||
await page.getByLabel('designer-schema-settings-Action.Link-actionSettings:link-users').first().hover();
|
||||
await page.getByRole('menuitem', { name: 'Edit link' }).click();
|
||||
await page.getByLabel('block-item-users-URL').getByLabel('textbox').click();
|
||||
await page
|
||||
.getByLabel('block-item-users-URL')
|
||||
.getByLabel('textbox')
|
||||
.fill(await nocoPage.getUrl());
|
||||
await page.getByPlaceholder('Name').fill('id');
|
||||
@ -102,10 +107,10 @@ test.describe('Link', () => {
|
||||
await page.getByLabel('action-Action.Link-Link-').hover();
|
||||
await page.getByLabel('designer-schema-settings-Action.Link-actionSettings:link-users').hover();
|
||||
await page.getByRole('menuitem', { name: 'Edit link' }).click();
|
||||
await page.getByLabel('block-item-users-table-URL').getByLabel('textbox').fill(otherPageUrl);
|
||||
await page.getByLabel('block-item-users-URL').getByLabel('textbox').fill(otherPageUrl);
|
||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||
|
||||
await page.getByLabel('action-Action.Link-Link-').click();
|
||||
await page.getByLabel('action-Action.Link-Link-').first().click();
|
||||
expect(page.url().endsWith(otherPageUrl)).toBe(true);
|
||||
|
||||
// 开启 “Open in new window” 选项后,点击链接按钮会在新窗口打开
|
||||
|
@ -15,6 +15,8 @@ import { ButtonEditor, RemoveButton } from '../../../schema-component/antd/actio
|
||||
import { SchemaSettingOpenModeSchemaItems } from '../../../schema-items';
|
||||
import { SchemaSettingsEnableChildCollections } from '../../../schema-settings/SchemaSettings';
|
||||
import { useOpenModeContext } from '../../popup/OpenModeProvider';
|
||||
import { SchemaSettingsLinkageRules } from '../../../schema-settings';
|
||||
import { useDataBlockProps } from '../../../data-source';
|
||||
|
||||
export const addNewActionSettings = new SchemaSettings({
|
||||
name: 'actionSettings:addNew',
|
||||
@ -27,6 +29,16 @@ export const addNewActionSettings = new SchemaSettings({
|
||||
return buttonEditorProps;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'linkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { linkageRulesProps } = useSchemaToolbar();
|
||||
return {
|
||||
...linkageRulesProps,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'openMode',
|
||||
Component: SchemaSettingOpenModeSchemaItems,
|
||||
@ -57,6 +69,21 @@ export const addNewActionSettings = new SchemaSettings({
|
||||
return isChildCollectionAction;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'linkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { association } = useDataBlockProps() || {};
|
||||
const { name } = useCollection_deprecated();
|
||||
const { getCollectionField } = useCollectionManager_deprecated();
|
||||
const associationField = getCollectionField(association);
|
||||
const { linkageRulesProps } = useSchemaToolbar();
|
||||
return {
|
||||
...linkageRulesProps,
|
||||
collectionName: associationField?.collectionName || name,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
sort: 100,
|
||||
|
@ -7,7 +7,7 @@
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import React, { useState, useContext } from 'react';
|
||||
import React, { useState, useContext, useEffect } from 'react';
|
||||
import { RecordPickerProvider, RecordPickerContext } from '../../../schema-component/antd/record-picker';
|
||||
import {
|
||||
SchemaComponentOptions,
|
||||
@ -41,9 +41,16 @@ const useTableSelectorProps = () => {
|
||||
export const AssociateActionProvider = (props) => {
|
||||
const [selectedRows, setSelectedRows] = useState([]);
|
||||
const collection = useCollection();
|
||||
const { resource, service, block, __parent } = useBlockRequestContext();
|
||||
const { resource, block, __parent } = useBlockRequestContext();
|
||||
const actionCtx = useActionContext();
|
||||
const { isMobile } = useOpenModeContext() || {};
|
||||
const [associationData, setAssociationData] = useState([]);
|
||||
useEffect(() => {
|
||||
resource?.list?.().then((res) => {
|
||||
setAssociationData(res.data?.data || []);
|
||||
});
|
||||
}, [resource]);
|
||||
|
||||
const pickerProps = {
|
||||
size: 'small',
|
||||
onChange: props?.onChange,
|
||||
@ -73,8 +80,8 @@ export const AssociateActionProvider = (props) => {
|
||||
};
|
||||
const getFilter = () => {
|
||||
const targetKey = collection?.filterTargetKey || 'id';
|
||||
if (service.data?.data) {
|
||||
const list = service.data?.data.map((option) => option[targetKey]).filter(Boolean);
|
||||
if (associationData) {
|
||||
const list = associationData.map((option) => option[targetKey]).filter(Boolean);
|
||||
const filter = list.length ? { $and: [{ [`${targetKey}.$ne`]: list }] } : {};
|
||||
return filter;
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
SecondConFirm,
|
||||
RefreshDataBlockRequest,
|
||||
} from '../../../schema-component/antd/action/Action.Designer';
|
||||
import { SchemaSettingsLinkageRules } from '../../../schema-settings';
|
||||
|
||||
export const bulkDeleteActionSettings = new SchemaSettings({
|
||||
name: 'actionSettings:bulkDelete',
|
||||
@ -27,6 +28,16 @@ export const bulkDeleteActionSettings = new SchemaSettings({
|
||||
return buttonEditorProps;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'linkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { linkageRulesProps } = useSchemaToolbar();
|
||||
return {
|
||||
...linkageRulesProps,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'secondConFirm',
|
||||
Component: SecondConFirm,
|
||||
@ -40,6 +51,16 @@ export const bulkDeleteActionSettings = new SchemaSettings({
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'linkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { linkageRulesProps } = useSchemaToolbar();
|
||||
return {
|
||||
...linkageRulesProps,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'remove',
|
||||
sort: 100,
|
||||
|
@ -32,11 +32,9 @@ export const disassociateActionSettings = new SchemaSettings({
|
||||
name: 'linkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection_deprecated();
|
||||
const { linkageRulesProps } = useSchemaToolbar();
|
||||
return {
|
||||
...linkageRulesProps,
|
||||
collectionName: name,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
@ -14,7 +14,7 @@ import { useDesignable } from '../../..';
|
||||
import { useSchemaToolbar } from '../../../application';
|
||||
import { SchemaSettings } from '../../../application/schema-settings/SchemaSettings';
|
||||
import { RemoveButton } from '../../../schema-component/antd/action/Action.Designer';
|
||||
import { SchemaSettingsModalItem } from '../../../schema-settings';
|
||||
import { SchemaSettingsModalItem, SchemaSettingsLinkageRules } from '../../../schema-settings';
|
||||
|
||||
function ButtonEditor() {
|
||||
const field = useField();
|
||||
@ -110,6 +110,17 @@ export const expendableActionSettings = new SchemaSettings({
|
||||
return buttonEditorProps;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'linkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { linkageRulesProps } = useSchemaToolbar();
|
||||
|
||||
return {
|
||||
...linkageRulesProps,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'remove',
|
||||
sort: 100,
|
||||
|
@ -53,14 +53,33 @@ export const filterActionSettings = new SchemaSettings({
|
||||
default: fieldSchema?.['x-component-props']?.icon,
|
||||
'x-component-props': {},
|
||||
},
|
||||
onlyIcon: {
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Checkbox',
|
||||
title: t('Icon only'),
|
||||
default: fieldSchema?.['x-component-props']?.onlyIcon,
|
||||
'x-component-props': {},
|
||||
'x-reactions': [
|
||||
{
|
||||
dependencies: ['icon'],
|
||||
fulfill: {
|
||||
state: {
|
||||
hidden: '{{!$deps[0]}}',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
} as ISchema,
|
||||
onSubmit: ({ title, icon }) => {
|
||||
onSubmit: ({ title, icon, onlyIcon }) => {
|
||||
fieldSchema.title = title;
|
||||
field.title = title;
|
||||
field.componentProps.icon = icon;
|
||||
field.componentProps.onlyIcon = onlyIcon;
|
||||
fieldSchema['x-component-props'] = fieldSchema['x-component-props'] || {};
|
||||
fieldSchema['x-component-props'].icon = icon;
|
||||
fieldSchema['x-component-props'].onlyIcon = onlyIcon;
|
||||
dn.emit('patch', {
|
||||
schema: {
|
||||
['x-uid']: fieldSchema['x-uid'],
|
||||
|
@ -11,17 +11,18 @@ import { useField, useFieldSchema } from '@formily/react';
|
||||
import _ from 'lodash';
|
||||
import React, { FC } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCollectionRecord, useDesignable } from '../../../';
|
||||
import { useDesignable } from '../../../';
|
||||
import { useSchemaToolbar } from '../../../application';
|
||||
import { SchemaSettings } from '../../../application/schema-settings/SchemaSettings';
|
||||
import { useCollection_deprecated } from '../../../collection-manager';
|
||||
import { ButtonEditor, RemoveButton } from '../../../schema-component/antd/action/Action.Designer';
|
||||
import { useCollectionManager_deprecated } from '../../../collection-manager';
|
||||
import {
|
||||
SchemaSettingsLinkageRules,
|
||||
SchemaSettingsModalItem,
|
||||
SchemaSettingAccessControl,
|
||||
} from '../../../schema-settings';
|
||||
import { useURLAndHTMLSchema } from './useURLAndHTMLSchema';
|
||||
import { useDataBlockProps } from '../../../data-source';
|
||||
|
||||
export const SchemaSettingsActionLinkItem: FC = () => {
|
||||
const field = useField();
|
||||
@ -94,16 +95,13 @@ export const customizeLinkActionSettings = new SchemaSettings({
|
||||
{
|
||||
name: 'linkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useVisible() {
|
||||
const record = useCollectionRecord();
|
||||
return !_.isEmpty(record?.data);
|
||||
},
|
||||
useComponentProps() {
|
||||
const { name } = useCollection_deprecated();
|
||||
const { linkageRulesProps } = useSchemaToolbar();
|
||||
const { association } = useDataBlockProps() || {};
|
||||
const { getCollectionField } = useCollectionManager_deprecated();
|
||||
const associationField = getCollectionField(association);
|
||||
return {
|
||||
...linkageRulesProps,
|
||||
collectionName: name,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
@ -10,7 +10,7 @@
|
||||
import { useSchemaToolbar } from '../../../application';
|
||||
import { SchemaSettings } from '../../../application/schema-settings/SchemaSettings';
|
||||
import { ButtonEditor, RemoveButton, SecondConFirm } from '../../../schema-component/antd/action/Action.Designer';
|
||||
|
||||
import { SchemaSettingsLinkageRules } from '../../../schema-settings';
|
||||
export const refreshActionSettings = new SchemaSettings({
|
||||
name: 'actionSettings:refresh',
|
||||
items: [
|
||||
@ -22,6 +22,17 @@ export const refreshActionSettings = new SchemaSettings({
|
||||
return buttonEditorProps;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'linkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { linkageRulesProps } = useSchemaToolbar();
|
||||
|
||||
return {
|
||||
...linkageRulesProps,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'secondConFirm',
|
||||
Component: SecondConFirm,
|
||||
|
@ -29,6 +29,7 @@ import { useCollectionState } from '../../../schema-settings/DataTemplates/hooks
|
||||
import { SchemaSettingsModalItem } from '../../../schema-settings/SchemaSettings';
|
||||
import { useParentPopupRecord } from '../../variable/variablesProvider/VariablePopupRecordProvider';
|
||||
import { useDataBlockProps } from '../../../data-source';
|
||||
import { SchemaSettingsLinkageRules } from '../../../schema-settings';
|
||||
|
||||
const Tree = connect(
|
||||
AntdTree,
|
||||
@ -149,6 +150,16 @@ export const createSubmitActionSettings = new SchemaSettings({
|
||||
return buttonEditorProps;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'linkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { linkageRulesProps } = useSchemaToolbar();
|
||||
return {
|
||||
...linkageRulesProps,
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'secondConfirmation',
|
||||
Component: SecondConFirm,
|
||||
|
@ -46,10 +46,6 @@ export const updateSubmitActionSettings = new SchemaSettings({
|
||||
collectionName: name,
|
||||
};
|
||||
},
|
||||
useVisible() {
|
||||
const fieldSchema = useFieldSchema();
|
||||
return !fieldSchema.parent['x-initializer'].includes('bulkEditForm');
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'secondConfirmation',
|
||||
|
@ -6,17 +6,12 @@
|
||||
* 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 { useFieldSchema } from '@formily/react';
|
||||
import { useSchemaToolbar } from '../../../application';
|
||||
import { SchemaSettings } from '../../../application/schema-settings/SchemaSettings';
|
||||
import { useCollection_deprecated } from '../../../collection-manager';
|
||||
import { useCollection } from '../../../data-source';
|
||||
import { ButtonEditor, RemoveButton } from '../../../schema-component/antd/action/Action.Designer';
|
||||
import { SchemaSettingOpenModeSchemaItems } from '../../../schema-items';
|
||||
import { SchemaSettingsLinkageRules, SchemaSettingAccessControl } from '../../../schema-settings';
|
||||
import { useOpenModeContext } from '../../popup/OpenModeProvider';
|
||||
import { useCurrentPopupRecord } from '../../variable/variablesProvider/VariablePopupRecordProvider';
|
||||
|
||||
export const customizePopupActionSettings = new SchemaSettings({
|
||||
name: 'actionSettings:popup',
|
||||
@ -33,18 +28,11 @@ export const customizePopupActionSettings = new SchemaSettings({
|
||||
name: 'linkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection_deprecated();
|
||||
const { linkageRulesProps } = useSchemaToolbar();
|
||||
return {
|
||||
...linkageRulesProps,
|
||||
collectionName: name,
|
||||
};
|
||||
},
|
||||
useVisible() {
|
||||
const { collection } = useCurrentPopupRecord() || {};
|
||||
const currentCollection = useCollection();
|
||||
return !collection || collection?.name === currentCollection?.name;
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'openMode',
|
||||
|
@ -0,0 +1,100 @@
|
||||
/**
|
||||
* 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 React, { useMemo, useEffect, useState } from 'react';
|
||||
import { useFieldSchema, useForm } from '@formily/react';
|
||||
import { last, isEqual } from 'lodash';
|
||||
import { uid } from '@formily/shared';
|
||||
import { reaction } from '@formily/reactive';
|
||||
import { useLocalVariables, useVariables } from '../../variables';
|
||||
import { useReactiveLinkageEffect } from './utils';
|
||||
import { useDesignable } from '../../';
|
||||
import { forEachLinkageRule } from '../../schema-settings/LinkageRules/forEachLinkageRule';
|
||||
import {
|
||||
getVariableValuesInCondition,
|
||||
getVariableValuesInExpression,
|
||||
} from '../../schema-settings/LinkageRules/bindLinkageRulesToFiled';
|
||||
|
||||
const getLinkageRules = (fieldSchema) => {
|
||||
if (!fieldSchema) {
|
||||
return [];
|
||||
}
|
||||
let linkageRules = fieldSchema?.['x-block-linkage-rules'] || [];
|
||||
fieldSchema.mapProperties((schema) => {
|
||||
if (schema['x-block-linkage-rules']) {
|
||||
linkageRules = schema['x-block-linkage-rules'];
|
||||
}
|
||||
});
|
||||
return linkageRules?.filter((k) => !k.disabled);
|
||||
};
|
||||
|
||||
export const BlockLinkageRuleProvider = (props) => {
|
||||
const schema = useFieldSchema();
|
||||
const variables = useVariables();
|
||||
const localVariables = useLocalVariables();
|
||||
const { designable } = useDesignable();
|
||||
const form = useForm();
|
||||
const linkageRules = useMemo(() => getLinkageRules(schema), [schema]);
|
||||
const [triggerLinkageUpdate, setTriggerLinkageUpdate] = useState(null);
|
||||
const displayResult = useReactiveLinkageEffect(linkageRules, variables, localVariables, triggerLinkageUpdate);
|
||||
const shouldCalculateFormLinkage = schema?.['x-decorator'] === 'FormItem' && !form.readPretty && linkageRules.length;
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldCalculateFormLinkage) {
|
||||
const id = uid();
|
||||
const disposes = [];
|
||||
|
||||
// 延迟执行,防止一开始获取到的 form.values 值是旧的
|
||||
setTimeout(() => {
|
||||
form.addEffects(id, () => {
|
||||
forEachLinkageRule(linkageRules, (action, rule) => {
|
||||
return reaction(
|
||||
() => {
|
||||
// 获取条件中的变量值
|
||||
const variableValuesInCondition = getVariableValuesInCondition({ linkageRules, localVariables });
|
||||
// 获取 value 表达式中的变量值
|
||||
const variableValuesInExpression = getVariableValuesInExpression({ action, localVariables });
|
||||
const result = [variableValuesInCondition, variableValuesInExpression]
|
||||
.map((item) => JSON.stringify(item))
|
||||
.join(',');
|
||||
return result;
|
||||
},
|
||||
() => {
|
||||
setTriggerLinkageUpdate(uid());
|
||||
},
|
||||
{ fireImmediately: true, equals: isEqual },
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 清理副作用
|
||||
return () => {
|
||||
form.removeEffects(id);
|
||||
disposes.forEach((dispose) => {
|
||||
dispose();
|
||||
});
|
||||
};
|
||||
}
|
||||
}, [linkageRules, shouldCalculateFormLinkage]);
|
||||
if (!linkageRules.length) {
|
||||
return props.children;
|
||||
}
|
||||
|
||||
if (displayResult === null) return null;
|
||||
if (last(displayResult) === 'hidden') {
|
||||
if (designable) {
|
||||
return <div style={{ opacity: 0.3 }}>{props.children}</div>;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return props.children;
|
||||
};
|
@ -45,7 +45,7 @@ test.describe('multi data details block schema settings', () => {
|
||||
// 禁用规则,联动规则失效
|
||||
await page.getByLabel('block-item-CardItem-users-').hover();
|
||||
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:detailsWithPagination-users').hover();
|
||||
await page.getByText('Linkage rules').click();
|
||||
await page.getByText('Field Linkage rules').click();
|
||||
await page.getByRole('switch', { name: 'On Off' }).click();
|
||||
await page.getByRole('button', { name: 'OK' }).click();
|
||||
await page.reload();
|
||||
|
@ -14,7 +14,7 @@ import { SchemaSettings } from '../../../../application/schema-settings/SchemaSe
|
||||
import { SchemaSettingsItemType } from '../../../../application/schema-settings/types';
|
||||
import { useDetailsBlockContext } from '../../../../block-provider/DetailsBlockProvider';
|
||||
import { useFormBlockContext } from '../../../../block-provider/FormBlockProvider';
|
||||
import { useCollection_deprecated, useSortFields } from '../../../../collection-manager';
|
||||
import { useSortFields } from '../../../../collection-manager';
|
||||
import { removeNullCondition, useDesignable } from '../../../../schema-component';
|
||||
import { SchemaSettingsLinkageRules } from '../../../../schema-settings';
|
||||
import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem';
|
||||
@ -24,6 +24,8 @@ import { SchemaSettingsTemplate } from '../../../../schema-settings/SchemaSettin
|
||||
import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider';
|
||||
import { setDataLoadingModeSettingsItem } from './setDataLoadingModeSettingsItem';
|
||||
import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem';
|
||||
import { LinkageRuleCategory } from '../../../../schema-settings/LinkageRules/type';
|
||||
import { useCollection } from '../../../../data-source';
|
||||
|
||||
const commonItems: SchemaSettingsItemType[] = [
|
||||
{
|
||||
@ -35,13 +37,28 @@ const commonItems: SchemaSettingsItemType[] = [
|
||||
Component: SchemaSettingsBlockHeightItem,
|
||||
},
|
||||
{
|
||||
name: 'linkageRules',
|
||||
name: 'fieldLinkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection_deprecated();
|
||||
const { name } = useCollection();
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
collectionName: name,
|
||||
readPretty: true,
|
||||
title: t('Field Linkage rules'),
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'blockLinkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection();
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
collectionName: name,
|
||||
title: t('Block Linkage rules'),
|
||||
category: LinkageRuleCategory.block,
|
||||
};
|
||||
},
|
||||
},
|
||||
@ -49,7 +66,7 @@ const commonItems: SchemaSettingsItemType[] = [
|
||||
name: 'dataScope',
|
||||
Component: SchemaSettingsDataScope,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection_deprecated();
|
||||
const { name } = useCollection();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { form } = useFormBlockContext();
|
||||
const field = useField();
|
||||
@ -83,7 +100,7 @@ const commonItems: SchemaSettingsItemType[] = [
|
||||
name: 'sortingRules',
|
||||
type: 'modal',
|
||||
useComponentProps() {
|
||||
const { name } = useCollection_deprecated();
|
||||
const { name } = useCollection();
|
||||
const { t } = useTranslation();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const field = useField();
|
||||
@ -201,7 +218,7 @@ const commonItems: SchemaSettingsItemType[] = [
|
||||
name: 'template',
|
||||
Component: SchemaSettingsTemplate,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection_deprecated();
|
||||
const { name } = useCollection();
|
||||
const fieldSchema = useFieldSchema();
|
||||
const { componentNamePrefix } = useBlockTemplateContext();
|
||||
const defaultResource =
|
||||
|
@ -8,14 +8,17 @@
|
||||
*/
|
||||
|
||||
import { useFieldSchema } from '@formily/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SchemaSettings } from '../../../../application/schema-settings/SchemaSettings';
|
||||
import { SchemaSettingsItemType } from '../../../../application/schema-settings/types';
|
||||
import { useCollection } from '../../../../data-source/collection/CollectionProvider';
|
||||
import { useCollection_deprecated } from '../../../../collection-manager';
|
||||
import { SchemaSettingsFormItemTemplate, SchemaSettingsLinkageRules } from '../../../../schema-settings';
|
||||
import { SchemaSettingsBlockHeightItem } from '../../../../schema-settings/SchemaSettingsBlockHeightItem';
|
||||
import { SchemaSettingsBlockTitleItem } from '../../../../schema-settings/SchemaSettingsBlockTitleItem';
|
||||
import { useBlockTemplateContext } from '../../../../schema-templates/BlockTemplateProvider';
|
||||
import { SchemaSettingsLayoutItem } from '../../../../schema-settings/SchemaSettingsLayoutItem';
|
||||
import { LinkageRuleCategory } from '../../../../schema-settings/LinkageRules/type';
|
||||
|
||||
const commonItems: SchemaSettingsItemType[] = [
|
||||
{
|
||||
@ -27,13 +30,28 @@ const commonItems: SchemaSettingsItemType[] = [
|
||||
Component: SchemaSettingsBlockHeightItem,
|
||||
},
|
||||
{
|
||||
name: 'linkageRules',
|
||||
name: 'fieldLinkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection();
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
collectionName: name,
|
||||
readPretty: true,
|
||||
title: t('Field Linkage rules'),
|
||||
};
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'blockLinkageRules',
|
||||
Component: SchemaSettingsLinkageRules,
|
||||
useComponentProps() {
|
||||
const { name } = useCollection_deprecated();
|
||||
const { t } = useTranslation();
|
||||
return {
|
||||
collectionName: name,
|
||||
title: t('Block Linkage rules'),
|
||||
category: LinkageRuleCategory.block,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
@ -310,7 +310,7 @@ test.describe('set default value', () => {
|
||||
// 设置联动规则
|
||||
await page.getByLabel('block-item-CardItem-users-form').hover();
|
||||
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:createForm-users').hover();
|
||||
await page.getByRole('menuitem', { name: 'Linkage rules' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Field linkage rules' }).click();
|
||||
await page.mouse.move(300, 0);
|
||||
await page.getByRole('button', { name: 'plus Add linkage rule' }).click();
|
||||
await page.getByText('Add property').click();
|
||||
@ -438,7 +438,7 @@ test.describe('set default value', () => {
|
||||
// 设置联动规则
|
||||
await page.getByLabel('block-item-CardItem-users-form').hover();
|
||||
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:createForm-users').hover();
|
||||
await page.getByRole('menuitem', { name: 'Linkage rules' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Field linkage rules' }).click();
|
||||
await page.mouse.move(300, 0);
|
||||
await page.getByRole('button', { name: 'plus Add linkage rule' }).click();
|
||||
await page.getByText('Add property').click();
|
||||
@ -563,7 +563,7 @@ test.describe('set default value', () => {
|
||||
// 设置联动规则
|
||||
await page.getByLabel('block-item-CardItem-users-form').hover();
|
||||
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:createForm-users').hover();
|
||||
await page.getByRole('menuitem', { name: 'Linkage rules' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Field linkage rules' }).click();
|
||||
await page.mouse.move(300, 0);
|
||||
await page.getByRole('button', { name: 'plus Add linkage rule' }).click();
|
||||
await page.getByText('Add property').click();
|
||||
@ -701,7 +701,7 @@ test.describe('set default value', () => {
|
||||
// 设置联动规则
|
||||
await page.getByLabel('block-item-CardItem-users-form').hover();
|
||||
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:createForm-users').hover();
|
||||
await page.getByRole('menuitem', { name: 'Linkage rules' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Field linkage rules' }).click();
|
||||
await page.mouse.move(300, 0);
|
||||
await page.getByRole('button', { name: 'plus Add linkage rule' }).click();
|
||||
await page.getByText('Add property').click();
|
||||
|
@ -35,11 +35,16 @@ test.describe('linkage rules', () => {
|
||||
// 条件:singleLineText 字段的值包含 123 时
|
||||
await page.getByRole('button', { name: 'plus Add linkage rule' }).click();
|
||||
await page.getByText('Add condition', { exact: true }).click();
|
||||
await page.getByTestId('select-filter-field').click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'singleLineText' }).click();
|
||||
await page.getByLabel('Linkage rules').locator('input[type="text"]').click();
|
||||
await page.getByLabel('Linkage rules').locator('input[type="text"]').fill('123');
|
||||
|
||||
await page.getByLabel('variable-button').first().click();
|
||||
await page.getByText('Current form').last().click();
|
||||
await page.getByText('Current form').last().click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'singleLineText' }).locator('div').click();
|
||||
|
||||
// await page.getByRole('menuitemcheckbox', { name: 'singleLineText' }).click();
|
||||
await page.getByTestId('right-filter-field').getByRole('textbox').click();
|
||||
await page.getByTestId('right-filter-field').getByRole('textbox').fill('123');
|
||||
await page.getByRole('tabpanel').getByRole('textbox').last().fill('123');
|
||||
// action:禁用 longText 字段
|
||||
await page.getByText('Add property').click();
|
||||
await page.getByTestId('select-linkage-property-field').click();
|
||||
@ -81,7 +86,7 @@ test.describe('linkage rules', () => {
|
||||
// 修改第一组规则,使其条件中包含一个变量 --------------------------------------------------------------------------
|
||||
// 当 singleLineText 字段的值包含 longText 字段的值时,禁用 longText 字段
|
||||
await openLinkageRules();
|
||||
await page.getByLabel('variable-button').click();
|
||||
await page.getByLabel('variable-button').last().click();
|
||||
await expectSupportedVariables(page, [
|
||||
'Constant',
|
||||
'Current user',
|
||||
@ -136,8 +141,13 @@ test.describe('linkage rules', () => {
|
||||
.getByText('Add condition', { exact: true })
|
||||
.last()
|
||||
.click();
|
||||
await page.getByRole('button', { name: 'Select field' }).click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'number' }).click();
|
||||
// await page.getByRole('button', { name: 'Select field' }).click();
|
||||
|
||||
await page.getByTestId('left-filter-field').getByLabel('variable-button').last().click();
|
||||
await page.getByText('Current form').last().click();
|
||||
await page.getByText('Current form').last().click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'number' }).locator('div').click();
|
||||
|
||||
await page.getByLabel('Linkage rules').getByRole('spinbutton').click();
|
||||
await page.getByLabel('Linkage rules').getByRole('spinbutton').fill('123');
|
||||
|
||||
|
@ -18,11 +18,11 @@ test.describe('deprecated variables', () => {
|
||||
await page.getByLabel('action-Action.Link-Edit').click();
|
||||
await page.getByLabel('block-item-CardItem-users-form').hover();
|
||||
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:editForm-users').hover();
|
||||
await page.getByRole('menuitem', { name: 'Linkage rules' }).click();
|
||||
await page.getByRole('menuitem', { name: 'Field linkage rules' }).click();
|
||||
await expect(page.getByLabel('variable-tag').getByText('Current record / Nickname')).toBeVisible();
|
||||
|
||||
// 2. 但是变量列表中是禁用状态
|
||||
await page.locator('button').filter({ hasText: /^x$/ }).click();
|
||||
await page.locator('button').filter({ hasText: /^x$/ }).last().click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'Current record right' }).hover({ position: { x: 40, y: 12 } });
|
||||
await expect(page.getByRole('tooltip', { name: 'This variable has been deprecated' })).toBeVisible();
|
||||
await expect(page.getByRole('menuitemcheckbox', { name: 'Current record right' })).toHaveClass(
|
||||
@ -45,11 +45,11 @@ test.describe('deprecated variables', () => {
|
||||
await page.getByLabel('Linkage rules').getByText('Linkage rules').click();
|
||||
|
||||
// 3. 当设置为其它变量后,再次打开,变量列表中的弃用变量不再显示
|
||||
await page.locator('button').filter({ hasText: /^x$/ }).click();
|
||||
await page.locator('button').filter({ hasText: /^x$/ }).last().click();
|
||||
await expect(page.getByRole('menuitemcheckbox', { name: 'Current form right' })).toHaveCount(1);
|
||||
await page.getByRole('menuitemcheckbox', { name: 'Current form right' }).click();
|
||||
await page.getByRole('menuitemcheckbox', { name: 'Nickname' }).click();
|
||||
await expect(page.getByLabel('variable-tag').getByText('Current form / Nickname')).toBeVisible();
|
||||
await expect(page.getByLabel('variable-tag').getByText('Current form / Nickname').last()).toBeVisible();
|
||||
// 清空表达式
|
||||
await page.getByLabel('textbox').clear();
|
||||
await page.getByRole('button', { name: 'OK', exact: true }).click();
|
||||
@ -57,8 +57,8 @@ test.describe('deprecated variables', () => {
|
||||
// 4. 再次打开弹窗,变量列表中的弃用变量不再显示
|
||||
await page.getByLabel('block-item-CardItem-users-form').hover();
|
||||
await page.getByLabel('designer-schema-settings-CardItem-blockSettings:editForm-users').hover();
|
||||
await page.getByRole('menuitem', { name: 'Linkage rules' }).click();
|
||||
await page.locator('button').filter({ hasText: /^x$/ }).click();
|
||||
await page.getByRole('menuitem', { name: 'Field linkage rules' }).click();
|
||||
await page.locator('button').filter({ hasText: /^x$/ }).last().click();
|
||||
await expect(page.getByRole('menuitemcheckbox', { name: 'Current record right' })).toBeHidden();
|
||||
// 使下拉菜单消失
|
||||
await page.getByLabel('Linkage rules').getByText('Linkage rules').click();
|
||||
|
@ -85,7 +85,7 @@ test.describe('configure fields', () => {
|
||||
await page.getByRole('menuitem', { name: 'manyToOne2 right' }).hover();
|
||||
await page.getByRole('menuitem', { name: 'manyToOne3' }).click();
|
||||
await page.mouse.move(600, 0);
|
||||
|
||||
await page.reload();
|
||||
await expect(page.getByLabel('block-item-CollectionField-general-form-general.manyToOne1-manyToOne1')).toHaveText(
|
||||
`manyToOne1:${record.manyToOne1.id}`,
|
||||
);
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user