mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 13:39:24 +08:00
Merge branch 'main' into 1320
This commit is contained in:
commit
0c1122a93f
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 }}
|
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
|
cancel-in-progress: true
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
workflow_dispatch:
|
||||||
branches:
|
|
||||||
- main
|
# push:
|
||||||
- next
|
# branches:
|
||||||
- develop
|
# - main
|
||||||
paths:
|
# - next
|
||||||
- 'package.json'
|
# - develop
|
||||||
- '**/yarn.lock'
|
# paths:
|
||||||
- 'packages/core/acl/**'
|
# - 'package.json'
|
||||||
- 'packages/core/auth/**'
|
# - '**/yarn.lock'
|
||||||
- 'packages/core/actions/**'
|
# - 'packages/core/acl/**'
|
||||||
- 'packages/core/database/**'
|
# - 'packages/core/auth/**'
|
||||||
- 'packages/core/resourcer/**'
|
# - 'packages/core/actions/**'
|
||||||
- 'packages/core/data-source-manager/**'
|
# - 'packages/core/database/**'
|
||||||
- 'packages/core/server/**'
|
# - 'packages/core/resourcer/**'
|
||||||
- 'packages/core/utils/**'
|
# - 'packages/core/data-source-manager/**'
|
||||||
- 'packages/plugins/**/src/server/**'
|
# - 'packages/core/server/**'
|
||||||
- '.github/workflows/nocobase-test-backend.yml'
|
# - 'packages/core/utils/**'
|
||||||
pull_request:
|
# - 'packages/plugins/**/src/server/**'
|
||||||
paths:
|
# - '.github/workflows/nocobase-test-backend.yml'
|
||||||
- 'package.json'
|
# pull_request:
|
||||||
- '**/yarn.lock'
|
# paths:
|
||||||
- 'packages/core/acl/**'
|
# - 'package.json'
|
||||||
- 'packages/core/auth/**'
|
# - '**/yarn.lock'
|
||||||
- 'packages/core/actions/**'
|
# - 'packages/core/acl/**'
|
||||||
- 'packages/core/database/**'
|
# - 'packages/core/auth/**'
|
||||||
- 'packages/core/resourcer/**'
|
# - 'packages/core/actions/**'
|
||||||
- 'packages/core/data-source-manager/**'
|
# - 'packages/core/database/**'
|
||||||
- 'packages/core/server/**'
|
# - 'packages/core/resourcer/**'
|
||||||
- 'packages/core/utils/**'
|
# - 'packages/core/data-source-manager/**'
|
||||||
- 'packages/plugins/**/src/server/**'
|
# - 'packages/core/server/**'
|
||||||
- '.github/workflows/nocobase-test-backend.yml'
|
# - 'packages/core/utils/**'
|
||||||
|
# - 'packages/plugins/**/src/server/**'
|
||||||
|
# - '.github/workflows/nocobase-test-backend.yml'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sqlite-test:
|
sqlite-test:
|
||||||
@ -59,7 +61,9 @@ jobs:
|
|||||||
node-version: ${{ matrix.node_version }}
|
node-version: ${{ matrix.node_version }}
|
||||||
cache: 'yarn'
|
cache: 'yarn'
|
||||||
- name: Install project dependencies
|
- name: Install project dependencies
|
||||||
run: yarn install
|
run: |
|
||||||
|
yarn install
|
||||||
|
yarn add sqlite3 --no-save -W
|
||||||
- name: Test with Sqlite
|
- name: Test with Sqlite
|
||||||
run: yarn test --server --single-thread=false
|
run: yarn test --server --single-thread=false
|
||||||
env:
|
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-
|
${{ runner.os }}-yarn-
|
||||||
|
|
||||||
- name: Install project dependencies
|
- name: Install project dependencies
|
||||||
run: yarn --prefer-offline
|
run: |
|
||||||
|
yarn --prefer-offline
|
||||||
|
yarn add sqlite3 --no-save -W
|
||||||
|
|
||||||
- name: Test with Sqlite
|
- name: Test with Sqlite
|
||||||
run: yarn test --server --single-thread=false
|
run: yarn test --server --single-thread=false
|
||||||
|
152
CHANGELOG.md
152
CHANGELOG.md
@ -5,6 +5,158 @@ 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/)
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [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
|
## [v1.6.18](https://github.com/nocobase/nocobase/compare/v1.6.17...v1.6.18) - 2025-04-11
|
||||||
|
|
||||||
### 🚀 Improvements
|
### 🚀 Improvements
|
||||||
|
@ -5,6 +5,158 @@
|
|||||||
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
|
格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
|
||||||
并且本项目遵循 [语义化版本](https://semver.org/spec/v2.0.0.html)。
|
并且本项目遵循 [语义化版本](https://semver.org/spec/v2.0.0.html)。
|
||||||
|
|
||||||
|
## [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
|
## [v1.6.18](https://github.com/nocobase/nocobase/compare/v1.6.17...v1.6.18) - 2025-04-11
|
||||||
|
|
||||||
### 🚀 优化
|
### 🚀 优化
|
||||||
|
@ -6,9 +6,13 @@ WORKDIR /app
|
|||||||
|
|
||||||
RUN cd /app \
|
RUN cd /app \
|
||||||
&& yarn config set network-timeout 600000 -g \
|
&& 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 \
|
&& 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 \
|
RUN cd /app \
|
||||||
&& rm -rf nocobase.tar.gz \
|
&& rm -rf nocobase.tar.gz \
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"npmClient": "yarn",
|
"npmClient": "yarn",
|
||||||
"useWorkspaces": true,
|
"useWorkspaces": true,
|
||||||
"npmClientArgs": ["--ignore-engines"],
|
"npmClientArgs": ["--ignore-engines"],
|
||||||
|
@ -81,6 +81,7 @@
|
|||||||
"ghooks": "^2.0.4",
|
"ghooks": "^2.0.4",
|
||||||
"lint-staged": "^13.2.3",
|
"lint-staged": "^13.2.3",
|
||||||
"patch-package": "^8.0.0",
|
"patch-package": "^8.0.0",
|
||||||
|
"pm2": "^6.0.5",
|
||||||
"pretty-format": "^24.0.0",
|
"pretty-format": "^24.0.0",
|
||||||
"pretty-quick": "^3.1.0",
|
"pretty-quick": "^3.1.0",
|
||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/acl",
|
"name": "@nocobase/acl",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/resourcer": "1.6.18",
|
"@nocobase/resourcer": "1.6.24",
|
||||||
"@nocobase/utils": "1.6.18",
|
"@nocobase/utils": "1.6.24",
|
||||||
"minimatch": "^5.1.1"
|
"minimatch": "^5.1.1"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/actions",
|
"name": "@nocobase/actions",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/cache": "1.6.18",
|
"@nocobase/cache": "1.6.24",
|
||||||
"@nocobase/database": "1.6.18",
|
"@nocobase/database": "1.6.24",
|
||||||
"@nocobase/resourcer": "1.6.18"
|
"@nocobase/resourcer": "1.6.24"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/app",
|
"name": "@nocobase/app",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/database": "1.6.18",
|
"@nocobase/database": "1.6.24",
|
||||||
"@nocobase/preset-nocobase": "1.6.18",
|
"@nocobase/preset-nocobase": "1.6.24",
|
||||||
"@nocobase/server": "1.6.18"
|
"@nocobase/server": "1.6.24"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nocobase/client": "1.6.18"
|
"@nocobase/client": "1.6.24"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/auth",
|
"name": "@nocobase/auth",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/actions": "1.6.18",
|
"@nocobase/actions": "1.6.24",
|
||||||
"@nocobase/cache": "1.6.18",
|
"@nocobase/cache": "1.6.24",
|
||||||
"@nocobase/database": "1.6.18",
|
"@nocobase/database": "1.6.24",
|
||||||
"@nocobase/resourcer": "1.6.18",
|
"@nocobase/resourcer": "1.6.24",
|
||||||
"@nocobase/utils": "1.6.18",
|
"@nocobase/utils": "1.6.24",
|
||||||
"@types/jsonwebtoken": "^8.5.8",
|
"@types/jsonwebtoken": "^9.0.9",
|
||||||
"jsonwebtoken": "^8.5.1"
|
"jsonwebtoken": "^9.0.2"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/build",
|
"name": "@nocobase/build",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"description": "Library build tool based on rollup.",
|
"description": "Library build tool based on rollup.",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
@ -17,7 +17,7 @@
|
|||||||
"@lerna/project": "4.0.0",
|
"@lerna/project": "4.0.0",
|
||||||
"@rsbuild/plugin-babel": "^1.0.3",
|
"@rsbuild/plugin-babel": "^1.0.3",
|
||||||
"@rsdoctor/rspack-plugin": "^0.4.8",
|
"@rsdoctor/rspack-plugin": "^0.4.8",
|
||||||
"@rspack/core": "1.1.1",
|
"@rspack/core": "1.3.2",
|
||||||
"@svgr/webpack": "^8.1.0",
|
"@svgr/webpack": "^8.1.0",
|
||||||
"@types/gulp": "^4.0.13",
|
"@types/gulp": "^4.0.13",
|
||||||
"@types/lerna__package": "5.1.0",
|
"@types/lerna__package": "5.1.0",
|
||||||
@ -39,7 +39,7 @@
|
|||||||
"postcss-preset-env": "^9.1.2",
|
"postcss-preset-env": "^9.1.2",
|
||||||
"react-imported-component": "^6.5.4",
|
"react-imported-component": "^6.5.4",
|
||||||
"style-loader": "^3.3.3",
|
"style-loader": "^3.3.3",
|
||||||
"tar": "^6.2.0",
|
"tar": "^7.4.3",
|
||||||
"tsup": "8.2.4",
|
"tsup": "8.2.4",
|
||||||
"typescript": "5.1.3",
|
"typescript": "5.1.3",
|
||||||
"update-notifier": "3.0.0",
|
"update-notifier": "3.0.0",
|
||||||
|
@ -347,6 +347,7 @@ export async function buildPluginClient(cwd: string, userConfig: UserConfig, sou
|
|||||||
umdNamedDefine: true,
|
umdNamedDefine: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
amd: {},
|
||||||
resolve: {
|
resolve: {
|
||||||
tsConfig: path.join(process.cwd(), 'tsconfig.json'),
|
tsConfig: path.join(process.cwd(), 'tsconfig.json'),
|
||||||
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json', '.less', '.css'],
|
extensions: ['.js', '.jsx', '.ts', '.tsx', '.json', '.less', '.css'],
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import tar from 'tar';
|
import { create } from 'tar';
|
||||||
import fg from 'fast-glob';
|
import fg from 'fast-glob';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
|
|
||||||
@ -38,5 +38,5 @@ export function tarPlugin(cwd: string, log: PkgLog) {
|
|||||||
|
|
||||||
fs.mkdirpSync(path.dirname(tarball));
|
fs.mkdirpSync(path.dirname(tarball));
|
||||||
fs.rmSync(tarball, { force: true });
|
fs.rmSync(tarball, { force: true });
|
||||||
return tar.c({ gzip: true, file: tarball, cwd }, tarFiles);
|
return create({ gzip: true, file: tarball, cwd }, tarFiles);
|
||||||
}
|
}
|
||||||
|
2
packages/core/cache/package.json
vendored
2
packages/core/cache/package.json
vendored
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/cache",
|
"name": "@nocobase/cache",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/cli",
|
"name": "@nocobase/cli",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./src/index.js",
|
"main": "./src/index.js",
|
||||||
@ -8,24 +8,25 @@
|
|||||||
"nocobase": "./bin/index.js"
|
"nocobase": "./bin/index.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/app": "1.6.18",
|
"@nocobase/app": "1.6.24",
|
||||||
"@types/fs-extra": "^11.0.1",
|
"@types/fs-extra": "^11.0.1",
|
||||||
"@umijs/utils": "3.5.20",
|
"@umijs/utils": "3.5.20",
|
||||||
"chalk": "^4.1.1",
|
"chalk": "^4.1.1",
|
||||||
"commander": "^9.2.0",
|
"commander": "^9.2.0",
|
||||||
|
"deepmerge": "^4.3.1",
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
"execa": "^5.1.1",
|
"execa": "^5.1.1",
|
||||||
"fast-glob": "^3.3.1",
|
"fast-glob": "^3.3.1",
|
||||||
"fs-extra": "^11.1.1",
|
"fs-extra": "^11.1.1",
|
||||||
"p-all": "3.0.0",
|
"p-all": "3.0.0",
|
||||||
"pm2": "^5.2.0",
|
"pm2": "^6.0.5",
|
||||||
"portfinder": "^1.0.28",
|
"portfinder": "^1.0.28",
|
||||||
"serve": "^13.0.2",
|
"tar": "^7.4.3",
|
||||||
"tree-kill": "^1.2.2",
|
"tree-kill": "^1.2.2",
|
||||||
"tsx": "^4.19.0"
|
"tsx": "^4.19.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nocobase/devtools": "1.6.18"
|
"@nocobase/devtools": "1.6.24"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const { Command } = require('commander');
|
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 { getPortPromise } = require('portfinder');
|
||||||
const chokidar = require('chokidar');
|
const chokidar = require('chokidar');
|
||||||
const { uid } = require('@formily/shared');
|
const { uid } = require('@formily/shared');
|
||||||
@ -36,6 +36,7 @@ module.exports = (cli) => {
|
|||||||
.option('-i, --inspect [port]')
|
.option('-i, --inspect [port]')
|
||||||
.allowUnknownOption()
|
.allowUnknownOption()
|
||||||
.action(async (opts) => {
|
.action(async (opts) => {
|
||||||
|
checkDBDialect();
|
||||||
let subprocess;
|
let subprocess;
|
||||||
const runDevClient = () => {
|
const runDevClient = () => {
|
||||||
console.log('starting client', 1 * clientPort);
|
console.log('starting client', 1 * clientPort);
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const { Command } = require('commander');
|
const { Command } = require('commander');
|
||||||
const { run, isPortReachable } = require('../util');
|
const { run, isPortReachable, checkDBDialect } = require('../util');
|
||||||
const { execSync } = require('node:child_process');
|
const { execSync } = require('node:child_process');
|
||||||
const axios = require('axios');
|
const axios = require('axios');
|
||||||
const { pTest } = require('./p-test');
|
const { pTest } = require('./p-test');
|
||||||
@ -165,6 +165,7 @@ const filterArgv = () => {
|
|||||||
*/
|
*/
|
||||||
module.exports = (cli) => {
|
module.exports = (cli) => {
|
||||||
const e2e = cli.command('e2e').hook('preAction', () => {
|
const e2e = cli.command('e2e').hook('preAction', () => {
|
||||||
|
checkDBDialect();
|
||||||
if (process.env.APP_BASE_URL) {
|
if (process.env.APP_BASE_URL) {
|
||||||
process.env.APP_BASE_URL = process.env.APP_BASE_URL.replace('localhost', '127.0.0.1');
|
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);
|
console.log('APP_BASE_URL:', process.env.APP_BASE_URL);
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const { Command } = require('commander');
|
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('-h, --help')
|
||||||
.option('--ts-node-dev')
|
.option('--ts-node-dev')
|
||||||
.action(async (options) => {
|
.action(async (options) => {
|
||||||
|
checkDBDialect();
|
||||||
const cmd = process.argv.slice(2)?.[0];
|
const cmd = process.argv.slice(2)?.[0];
|
||||||
if (cmd === 'install') {
|
if (cmd === 'install') {
|
||||||
await downloadPro();
|
await downloadPro();
|
||||||
|
@ -30,6 +30,7 @@ module.exports = (cli) => {
|
|||||||
require('./test')(cli);
|
require('./test')(cli);
|
||||||
require('./test-coverage')(cli);
|
require('./test-coverage')(cli);
|
||||||
require('./umi')(cli);
|
require('./umi')(cli);
|
||||||
|
require('./update-deps')(cli);
|
||||||
require('./upgrade')(cli);
|
require('./upgrade')(cli);
|
||||||
require('./postinstall')(cli);
|
require('./postinstall')(cli);
|
||||||
require('./pkg')(cli);
|
require('./pkg')(cli);
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const { Command } = require('commander');
|
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 { existsSync, rmSync } = require('fs');
|
||||||
const { resolve, isAbsolute } = require('path');
|
const { resolve, isAbsolute } = require('path');
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
@ -48,8 +48,10 @@ module.exports = (cli) => {
|
|||||||
.option('-i, --instances [instances]')
|
.option('-i, --instances [instances]')
|
||||||
.option('--db-sync')
|
.option('--db-sync')
|
||||||
.option('--quickstart')
|
.option('--quickstart')
|
||||||
|
.option('--launch-mode [launchMode]')
|
||||||
.allowUnknownOption()
|
.allowUnknownOption()
|
||||||
.action(async (opts) => {
|
.action(async (opts) => {
|
||||||
|
checkDBDialect();
|
||||||
if (opts.quickstart) {
|
if (opts.quickstart) {
|
||||||
await downloadPro();
|
await downloadPro();
|
||||||
}
|
}
|
||||||
@ -118,6 +120,8 @@ module.exports = (cli) => {
|
|||||||
]);
|
]);
|
||||||
process.exit();
|
process.exit();
|
||||||
} else {
|
} else {
|
||||||
|
const launchMode = opts.launchMode || process.env.APP_LAUNCH_MODE || 'pm2';
|
||||||
|
if (launchMode === 'pm2') {
|
||||||
run(
|
run(
|
||||||
'pm2-runtime',
|
'pm2-runtime',
|
||||||
[
|
[
|
||||||
@ -129,6 +133,14 @@ module.exports = (cli) => {
|
|||||||
...process.argv.slice(2),
|
...process.argv.slice(2),
|
||||||
].filter(Boolean),
|
].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.
|
* 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 fg = require('fast-glob');
|
||||||
|
|
||||||
const coreClientPackages = ['packages/core/client', 'packages/core/sdk'];
|
const coreClientPackages = ['packages/core/client', 'packages/core/sdk'];
|
||||||
@ -30,6 +30,7 @@ const getPackagesDir = (isClient) => {
|
|||||||
|
|
||||||
module.exports = (cli) => {
|
module.exports = (cli) => {
|
||||||
cli.command('test-coverage:server').action(async () => {
|
cli.command('test-coverage:server').action(async () => {
|
||||||
|
checkDBDialect();
|
||||||
const packageRoots = getPackagesDir(false);
|
const packageRoots = getPackagesDir(false);
|
||||||
for (const dir of packageRoots) {
|
for (const dir of packageRoots) {
|
||||||
try {
|
try {
|
||||||
@ -41,6 +42,7 @@ module.exports = (cli) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
cli.command('test-coverage:client').action(async () => {
|
cli.command('test-coverage:client').action(async () => {
|
||||||
|
checkDBDialect();
|
||||||
const packageRoots = getPackagesDir(true);
|
const packageRoots = getPackagesDir(true);
|
||||||
for (const dir of packageRoots) {
|
for (const dir of packageRoots) {
|
||||||
try {
|
try {
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const { Command } = require('commander');
|
const { Command } = require('commander');
|
||||||
const { run } = require('../util');
|
const { run, checkDBDialect } = require('../util');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -29,6 +29,7 @@ function addTestCommand(name, cli) {
|
|||||||
.arguments('[paths...]')
|
.arguments('[paths...]')
|
||||||
.allowUnknownOption()
|
.allowUnknownOption()
|
||||||
.action(async (paths, opts) => {
|
.action(async (paths, opts) => {
|
||||||
|
checkDBDialect();
|
||||||
if (name === 'test:server') {
|
if (name === 'test:server') {
|
||||||
process.env.TEST_ENV = 'server-side';
|
process.env.TEST_ENV = 'server-side';
|
||||||
} else if (name === 'test:client') {
|
} 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 chalk = require('chalk');
|
||||||
const { Command } = require('commander');
|
const { Command } = require('commander');
|
||||||
const { resolve } = require('path');
|
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 { 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
|
* @param {Command} cli
|
||||||
*/
|
*/
|
||||||
module.exports = (cli) => {
|
module.exports = (cli) => {
|
||||||
const { APP_PACKAGE_ROOT } = process.env;
|
|
||||||
cli
|
cli
|
||||||
.command('upgrade')
|
.command('upgrade')
|
||||||
.allowUnknownOption()
|
.allowUnknownOption()
|
||||||
@ -26,52 +36,12 @@ module.exports = (cli) => {
|
|||||||
.option('--next')
|
.option('--next')
|
||||||
.option('-S|--skip-code-update')
|
.option('-S|--skip-code-update')
|
||||||
.action(async (options) => {
|
.action(async (options) => {
|
||||||
if (hasTsNode()) promptForTs();
|
checkDBDialect();
|
||||||
if (hasCorePackages()) {
|
|
||||||
// await run('yarn', ['install']);
|
|
||||||
await downloadPro();
|
|
||||||
await runAppCommand('upgrade');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (options.skipCodeUpdate) {
|
if (options.skipCodeUpdate) {
|
||||||
await downloadPro();
|
|
||||||
await runAppCommand('upgrade');
|
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_BASE_PATH: '/api/',
|
||||||
API_CLIENT_STORAGE_PREFIX: 'NOCOBASE_',
|
API_CLIENT_STORAGE_PREFIX: 'NOCOBASE_',
|
||||||
API_CLIENT_STORAGE_TYPE: 'localStorage',
|
API_CLIENT_STORAGE_TYPE: 'localStorage',
|
||||||
DB_DIALECT: 'sqlite',
|
// DB_DIALECT: 'sqlite',
|
||||||
DB_STORAGE: 'storage/db/nocobase.sqlite',
|
DB_STORAGE: 'storage/db/nocobase.sqlite',
|
||||||
// DB_TIMEZONE: '+00:00',
|
// DB_TIMEZONE: '+00:00',
|
||||||
DB_UNDERSCORED: parseEnv('DB_UNDERSCORED'),
|
DB_UNDERSCORED: parseEnv('DB_UNDERSCORED'),
|
||||||
@ -460,8 +460,22 @@ exports.initEnv = function initEnv() {
|
|||||||
process.env.SOCKET_PATH = generateGatewayPath();
|
process.env.SOCKET_PATH = generateGatewayPath();
|
||||||
fs.mkdirpSync(dirname(process.env.SOCKET_PATH), { force: true, recursive: true });
|
fs.mkdirpSync(dirname(process.env.SOCKET_PATH), { force: true, recursive: true });
|
||||||
fs.mkdirpSync(process.env.PM2_HOME, { 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');
|
const pkgs = [
|
||||||
fs.existsSync(pkgDir) && fs.rmdirSync(pkgDir, { force: true });
|
'@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 () {
|
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"
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/client",
|
"name": "@nocobase/client",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"module": "es/index.mjs",
|
"module": "es/index.mjs",
|
||||||
@ -27,9 +27,9 @@
|
|||||||
"@formily/reactive-react": "^2.2.27",
|
"@formily/reactive-react": "^2.2.27",
|
||||||
"@formily/shared": "^2.2.27",
|
"@formily/shared": "^2.2.27",
|
||||||
"@formily/validator": "^2.2.27",
|
"@formily/validator": "^2.2.27",
|
||||||
"@nocobase/evaluators": "1.6.18",
|
"@nocobase/evaluators": "1.6.24",
|
||||||
"@nocobase/sdk": "1.6.18",
|
"@nocobase/sdk": "1.6.24",
|
||||||
"@nocobase/utils": "1.6.18",
|
"@nocobase/utils": "1.6.24",
|
||||||
"ahooks": "^3.7.2",
|
"ahooks": "^3.7.2",
|
||||||
"antd": "5.12.8",
|
"antd": "5.12.8",
|
||||||
"antd-style": "3.7.1",
|
"antd-style": "3.7.1",
|
||||||
|
@ -309,15 +309,17 @@ export const ACLActionProvider = (props) => {
|
|||||||
const schema = useFieldSchema();
|
const schema = useFieldSchema();
|
||||||
const currentUid = schema['x-uid'];
|
const currentUid = schema['x-uid'];
|
||||||
let actionPath = schema['x-acl-action'];
|
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']}`;
|
actionPath = `${resource}:${schema['x-action']}`;
|
||||||
}
|
}
|
||||||
if (actionPath && !actionPath?.includes(':')) {
|
if (actionPath && !actionPath?.includes(':')) {
|
||||||
actionPath = `${resource}:${actionPath}`;
|
actionPath = `${resource}:${actionPath}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = useMemo(
|
const params = useMemo(
|
||||||
() => actionPath && parseAction(actionPath, { schema, recordPkValue }),
|
() => actionPath && parseAction(actionPath, { schema, recordPkValue }),
|
||||||
[parseAction, actionPath, schema, recordPkValue],
|
[parseAction, actionPath, schema, recordPkValue],
|
||||||
@ -334,16 +336,18 @@ export const ACLActionProvider = (props) => {
|
|||||||
if (!params) {
|
if (!params) {
|
||||||
return <ACLActionParamsContext.Provider value={params}>{props.children}</ACLActionParamsContext.Provider>;
|
return <ACLActionParamsContext.Provider value={params}>{props.children}</ACLActionParamsContext.Provider>;
|
||||||
}
|
}
|
||||||
//视图表无编辑权限时不显示
|
//视图表无编辑权限时不支持 writableViewCollectionAction 的按钮
|
||||||
if (editablePath.includes(actionPath) || editablePath.includes(actionPath?.split(':')[1])) {
|
if (
|
||||||
|
writableViewCollectionAction.includes(actionPath) ||
|
||||||
|
writableViewCollectionAction.includes(actionPath?.split(':')[1])
|
||||||
|
) {
|
||||||
if ((collection && collection.template !== 'view') || collection?.writableView) {
|
if ((collection && collection.template !== 'view') || collection?.writableView) {
|
||||||
return <ACLActionParamsContext.Provider value={params}>{props.children}</ACLActionParamsContext.Provider>;
|
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>;
|
return <ACLActionParamsContext.Provider value={params}>{props.children}</ACLActionParamsContext.Provider>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useACLFieldWhitelist = () => {
|
export const useACLFieldWhitelist = () => {
|
||||||
const params = useContext(ACLActionParamsContext);
|
const params = useContext(ACLActionParamsContext);
|
||||||
const whitelist = useMemo(() => {
|
const whitelist = useMemo(() => {
|
||||||
|
@ -10,11 +10,11 @@
|
|||||||
import { useFieldSchema } from '@formily/react';
|
import { useFieldSchema } from '@formily/react';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { withDynamicSchemaProps } from '../hoc/withDynamicSchemaProps';
|
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 { DefaultValueProvider } from '../schema-settings';
|
||||||
import { CollectOperators } from './CollectOperators';
|
import { CollectOperators } from './CollectOperators';
|
||||||
import { FormBlockProvider } from './FormBlockProvider';
|
import { FormBlockProvider } from './FormBlockProvider';
|
||||||
import { FilterCollectionField } from '../modules/blocks/filter-blocks/FilterCollectionField';
|
|
||||||
|
|
||||||
export const FilterFormBlockProvider = withDynamicSchemaProps((props) => {
|
export const FilterFormBlockProvider = withDynamicSchemaProps((props) => {
|
||||||
const filedSchema = useFieldSchema();
|
const filedSchema = useFieldSchema();
|
||||||
@ -35,7 +35,7 @@ export const FilterFormBlockProvider = withDynamicSchemaProps((props) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DefaultValueProvider isAllowToSetDefaultValue={() => false}>
|
<DefaultValueProvider isAllowToSetDefaultValue={() => false}>
|
||||||
<FormBlockProvider name="filter-form" {...props}></FormBlockProvider>
|
<FormBlockProvider name="filter-form" {...props} confirmBeforeClose={false}></FormBlockProvider>
|
||||||
</DefaultValueProvider>
|
</DefaultValueProvider>
|
||||||
</ActionBarProvider>
|
</ActionBarProvider>
|
||||||
</DatePickerProvider>
|
</DatePickerProvider>
|
||||||
|
@ -97,6 +97,30 @@ const filterValue = (value) => {
|
|||||||
return obj;
|
return obj;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getFilteredFormValues(form) {
|
||||||
|
const values = _.cloneDeep(form.values);
|
||||||
|
const allFields = [];
|
||||||
|
form.query('*').forEach((field) => {
|
||||||
|
if (field) {
|
||||||
|
allFields.push(field);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const readonlyPaths = 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('.');
|
||||||
|
});
|
||||||
|
for (const path of readonlyPaths) {
|
||||||
|
_.unset(values, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
export function getFormValues({
|
export function getFormValues({
|
||||||
filterByTk,
|
filterByTk,
|
||||||
field,
|
field,
|
||||||
@ -124,7 +148,7 @@ export function getFormValues({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return form.values;
|
return getFilteredFormValues(form);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useCollectValuesToSubmit() {
|
export function useCollectValuesToSubmit() {
|
||||||
@ -522,9 +546,11 @@ export const useFilterBlockActionProps = () => {
|
|||||||
const { doFilter } = useDoFilter();
|
const { doFilter } = useDoFilter();
|
||||||
const actionField = useField();
|
const actionField = useField();
|
||||||
actionField.data = actionField.data || {};
|
actionField.data = actionField.data || {};
|
||||||
|
const form = useForm();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
async onClick() {
|
async onClick() {
|
||||||
|
await form.submit();
|
||||||
actionField.data.loading = true;
|
actionField.data.loading = true;
|
||||||
await doFilter();
|
await doFilter();
|
||||||
actionField.data.loading = false;
|
actionField.data.loading = false;
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* 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 type { CollectionFieldOptions, GetCollectionFieldPredicate } from '../../data-source';
|
||||||
import { Collection } from '../../data-source/collection/Collection';
|
import { Collection } from '../../data-source/collection/Collection';
|
||||||
import _, { filter, unionBy, uniq } from 'lodash';
|
|
||||||
|
|
||||||
export class InheritanceCollectionMixin extends Collection {
|
export class InheritanceCollectionMixin extends Collection {
|
||||||
protected parentCollectionsName: string[];
|
protected parentCollectionsName: string[];
|
||||||
@ -22,6 +22,7 @@ export class InheritanceCollectionMixin extends Collection {
|
|||||||
protected parentCollectionFields: Record<string, CollectionFieldOptions[]> = {};
|
protected parentCollectionFields: Record<string, CollectionFieldOptions[]> = {};
|
||||||
protected allCollectionsInheritChain: string[];
|
protected allCollectionsInheritChain: string[];
|
||||||
protected inheritCollectionsChain: string[];
|
protected inheritCollectionsChain: string[];
|
||||||
|
protected inheritChain: string[];
|
||||||
protected foreignKeyFields: CollectionFieldOptions[];
|
protected foreignKeyFields: CollectionFieldOptions[];
|
||||||
|
|
||||||
getParentCollectionsName() {
|
getParentCollectionsName() {
|
||||||
@ -233,6 +234,43 @@ export class InheritanceCollectionMixin extends Collection {
|
|||||||
return this.inheritCollectionsChain;
|
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) {
|
getAllFields(predicate?: GetCollectionFieldPredicate) {
|
||||||
if (this.allFields) {
|
if (this.allFields) {
|
||||||
return this.allFields.slice();
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -29,7 +29,7 @@ export function useDataSourceManager() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前 collection 继承链路上的所有 collection
|
* 获取当前 collection 继承链路上的所有 collection(不包括兄弟表)
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function useAllCollectionsInheritChainGetter() {
|
export function useAllCollectionsInheritChainGetter() {
|
||||||
@ -39,7 +39,7 @@ export function useAllCollectionsInheritChainGetter() {
|
|||||||
return dm
|
return dm
|
||||||
?.getDataSource(customDataSource)
|
?.getDataSource(customDataSource)
|
||||||
?.collectionManager?.getCollection<InheritanceCollectionMixin>(collectionName)
|
?.collectionManager?.getCollection<InheritanceCollectionMixin>(collectionName)
|
||||||
?.getAllCollectionsInheritChain();
|
?.getInheritChain();
|
||||||
},
|
},
|
||||||
[dm],
|
[dm],
|
||||||
);
|
);
|
||||||
|
@ -67,6 +67,108 @@ describe('getSupportFieldsByAssociation', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('getSupportFieldsByForeignKey', () => {
|
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", () => {
|
it("should return all foreign key fields matching the filter block collection's foreign key properties", () => {
|
||||||
const filterBlockCollection = {
|
const filterBlockCollection = {
|
||||||
fields: [
|
fields: [
|
||||||
|
@ -49,10 +49,14 @@ export const getSupportFieldsByAssociation = (inheritCollectionsChain: string[],
|
|||||||
|
|
||||||
export const getSupportFieldsByForeignKey = (filterBlockCollection: Collection, block: DataBlock) => {
|
export const getSupportFieldsByForeignKey = (filterBlockCollection: Collection, block: DataBlock) => {
|
||||||
return block.foreignKeyFields?.filter((foreignKeyField) => {
|
return block.foreignKeyFields?.filter((foreignKeyField) => {
|
||||||
return filterBlockCollection.fields.some(
|
return filterBlockCollection.fields.some((field) => {
|
||||||
(field) => field.type !== 'belongsTo' && field.foreignKey === foreignKeyField.name,
|
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(
|
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',
|
field: string | ((target: FilterTarget['targets'][0], block: DataBlock) => string) = 'id',
|
||||||
operator: string | ((target: FilterTarget['targets'][0]) => string) = '$eq',
|
operator: string | ((target: FilterTarget['targets'][0]) => string) = '$eq',
|
||||||
) => {
|
) => {
|
||||||
|
const currentBlock = dataBlocks.find((block) => block.uid === fieldSchema.parent['x-uid']);
|
||||||
dataBlocks.forEach((block) => {
|
dataBlocks.forEach((block) => {
|
||||||
|
let key = field as string;
|
||||||
const target = targets.find((target) => target.uid === block.uid);
|
const target = targets.find((target) => target.uid === block.uid);
|
||||||
if (!target) return;
|
if (!target) return;
|
||||||
|
|
||||||
if (_.isFunction(value)) {
|
if (_.isFunction(value)) {
|
||||||
value = value(target, block);
|
value = value(target, block, getSourceKey(currentBlock, target.field));
|
||||||
}
|
}
|
||||||
if (_.isFunction(field)) {
|
if (_.isFunction(field)) {
|
||||||
field = field(target, block);
|
key = field(target, block);
|
||||||
}
|
}
|
||||||
if (_.isFunction(operator)) {
|
if (_.isFunction(operator)) {
|
||||||
operator = operator(target);
|
operator = operator(target);
|
||||||
@ -219,7 +225,7 @@ export const useFilterAPI = () => {
|
|||||||
storedFilter[uid] = {
|
storedFilter[uid] = {
|
||||||
$and: [
|
$and: [
|
||||||
{
|
{
|
||||||
[field]: {
|
[key]: {
|
||||||
[operator]: value,
|
[operator]: value,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -248,7 +254,7 @@ export const useFilterAPI = () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[dataBlocks, targets, uid],
|
[dataBlocks, targets, uid, fieldSchema],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -268,3 +274,8 @@ export const isInFilterFormBlock = (fieldSchema: Schema) => {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getSourceKey(currentBlock: DataBlock, field: string) {
|
||||||
|
const associationField = currentBlock?.associatedFields?.find((item) => item.foreignKey === field);
|
||||||
|
return associationField?.sourceKey || field?.split?.('.')?.[1];
|
||||||
|
}
|
||||||
|
@ -818,7 +818,8 @@
|
|||||||
"File size should not exceed {{size}}.": "文件大小不能超过 {{size}}",
|
"File size should not exceed {{size}}.": "文件大小不能超过 {{size}}",
|
||||||
"File size exceeds the limit": "文件大小超过限制",
|
"File size exceeds the limit": "文件大小超过限制",
|
||||||
"File type is not allowed": "文件类型不允许",
|
"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": "用作数据的默认标题",
|
"Default title for each record": "用作数据的默认标题",
|
||||||
"If collection inherits, choose inherited collections as templates": "当前表有继承关系时,可选择继承链路上的表作为模板来源",
|
"If collection inherits, choose inherited collections as templates": "当前表有继承关系时,可选择继承链路上的表作为模板来源",
|
||||||
"Select an existing piece of data as the initialization data for the form": "选择一条已有的数据作为表单的初始化数据",
|
"Select an existing piece of data as the initialization data for the form": "选择一条已有的数据作为表单的初始化数据",
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* 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 { RecordPickerProvider, RecordPickerContext } from '../../../schema-component/antd/record-picker';
|
||||||
import {
|
import {
|
||||||
SchemaComponentOptions,
|
SchemaComponentOptions,
|
||||||
@ -41,9 +41,16 @@ const useTableSelectorProps = () => {
|
|||||||
export const AssociateActionProvider = (props) => {
|
export const AssociateActionProvider = (props) => {
|
||||||
const [selectedRows, setSelectedRows] = useState([]);
|
const [selectedRows, setSelectedRows] = useState([]);
|
||||||
const collection = useCollection();
|
const collection = useCollection();
|
||||||
const { resource, service, block, __parent } = useBlockRequestContext();
|
const { resource, block, __parent } = useBlockRequestContext();
|
||||||
const actionCtx = useActionContext();
|
const actionCtx = useActionContext();
|
||||||
const { isMobile } = useOpenModeContext() || {};
|
const { isMobile } = useOpenModeContext() || {};
|
||||||
|
const [associationData, setAssociationData] = useState([]);
|
||||||
|
useEffect(() => {
|
||||||
|
resource?.list?.().then((res) => {
|
||||||
|
setAssociationData(res.data?.data || []);
|
||||||
|
});
|
||||||
|
}, [resource]);
|
||||||
|
|
||||||
const pickerProps = {
|
const pickerProps = {
|
||||||
size: 'small',
|
size: 'small',
|
||||||
onChange: props?.onChange,
|
onChange: props?.onChange,
|
||||||
@ -73,8 +80,8 @@ export const AssociateActionProvider = (props) => {
|
|||||||
};
|
};
|
||||||
const getFilter = () => {
|
const getFilter = () => {
|
||||||
const targetKey = collection?.filterTargetKey || 'id';
|
const targetKey = collection?.filterTargetKey || 'id';
|
||||||
if (service.data?.data) {
|
if (associationData) {
|
||||||
const list = service.data?.data.map((option) => option[targetKey]).filter(Boolean);
|
const list = associationData.map((option) => option[targetKey]).filter(Boolean);
|
||||||
const filter = list.length ? { $and: [{ [`${targetKey}.$ne`]: list }] } : {};
|
const filter = list.length ? { $and: [{ [`${targetKey}.$ne`]: list }] } : {};
|
||||||
return filter;
|
return filter;
|
||||||
}
|
}
|
||||||
|
@ -306,6 +306,7 @@ test.describe('configure actions column', () => {
|
|||||||
await page.getByText('Actions', { exact: true }).hover();
|
await page.getByText('Actions', { exact: true }).hover();
|
||||||
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
||||||
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
await page.getByRole('menuitem', { name: 'Delete' }).click();
|
||||||
|
await page.mouse.move(500, 0);
|
||||||
|
|
||||||
await page.getByText('Actions', { exact: true }).hover();
|
await page.getByText('Actions', { exact: true }).hover();
|
||||||
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
await page.getByLabel('designer-schema-initializer-TableV2.Column-TableV2.ActionColumnDesigner-').hover();
|
||||||
|
@ -94,7 +94,11 @@ export const FilterCollectionFieldInternalField: React.FC = (props: Props) => {
|
|||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
field.dataSource = uiSchema.enum;
|
field.dataSource = uiSchema.enum;
|
||||||
const originalProps =
|
const originalProps =
|
||||||
compile({ ...(operator?.schema?.['x-component-props'] || {}), ...(uiSchema['x-component-props'] || {}) }) || {};
|
compile({
|
||||||
|
...(operator?.schema?.['x-component-props'] || {}),
|
||||||
|
...(uiSchema['x-component-props'] || {}),
|
||||||
|
...(fieldSchema?.['x-component-props'] || {}),
|
||||||
|
}) || {};
|
||||||
|
|
||||||
field.componentProps = merge(field.componentProps || {}, originalProps, dynamicProps || {});
|
field.componentProps = merge(field.componentProps || {}, originalProps, dynamicProps || {});
|
||||||
}, [uiSchemaOrigin]);
|
}, [uiSchemaOrigin]);
|
||||||
|
@ -0,0 +1,212 @@
|
|||||||
|
/**
|
||||||
|
* 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 { findFirstPageRoute, NocoBaseDesktopRouteType } from '..';
|
||||||
|
import { NocoBaseDesktopRoute } from '../convertRoutesToSchema';
|
||||||
|
|
||||||
|
describe('findFirstPageRoute', () => {
|
||||||
|
// 基本测试:空路由数组
|
||||||
|
it('should return undefined for empty routes array', () => {
|
||||||
|
const result = findFirstPageRoute([]);
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 基本测试:undefined 路由数组
|
||||||
|
it('should return undefined for undefined routes', () => {
|
||||||
|
const result = findFirstPageRoute(undefined);
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试:只有一个页面路由
|
||||||
|
it('should find the first page route when there is only one page', () => {
|
||||||
|
const routes: NocoBaseDesktopRoute[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
schemaUid: 'page1',
|
||||||
|
type: NocoBaseDesktopRouteType.page,
|
||||||
|
title: 'Page 1',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = findFirstPageRoute(routes);
|
||||||
|
expect(result).toEqual(routes[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试:多个页面路由
|
||||||
|
it('should find the first page route when there are multiple pages', () => {
|
||||||
|
const routes: NocoBaseDesktopRoute[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
schemaUid: 'page1',
|
||||||
|
type: NocoBaseDesktopRouteType.page,
|
||||||
|
title: 'Page 1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
schemaUid: 'page2',
|
||||||
|
type: NocoBaseDesktopRouteType.page,
|
||||||
|
title: 'Page 2',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = findFirstPageRoute(routes);
|
||||||
|
expect(result).toEqual(routes[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试:不同类型的路由混合
|
||||||
|
it('should find the first page route among mixed route types', () => {
|
||||||
|
const routes: NocoBaseDesktopRoute[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
schemaUid: 'link1',
|
||||||
|
type: NocoBaseDesktopRouteType.link,
|
||||||
|
title: 'Link 1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
schemaUid: 'page1',
|
||||||
|
type: NocoBaseDesktopRouteType.page,
|
||||||
|
title: 'Page 1',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = findFirstPageRoute(routes);
|
||||||
|
expect(result).toEqual(routes[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试:隐藏的菜单项
|
||||||
|
it('should ignore hidden menu items', () => {
|
||||||
|
const routes: NocoBaseDesktopRoute[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
schemaUid: 'page1',
|
||||||
|
type: NocoBaseDesktopRouteType.page,
|
||||||
|
title: 'Page 1',
|
||||||
|
hideInMenu: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
schemaUid: 'page2',
|
||||||
|
type: NocoBaseDesktopRouteType.page,
|
||||||
|
title: 'Page 2',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = findFirstPageRoute(routes);
|
||||||
|
expect(result).toEqual(routes[1]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试:嵌套路由
|
||||||
|
it('should find page route in nested group', () => {
|
||||||
|
const routes: NocoBaseDesktopRoute[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: NocoBaseDesktopRouteType.group,
|
||||||
|
title: 'Group 1',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
schemaUid: 'page1',
|
||||||
|
type: NocoBaseDesktopRouteType.page,
|
||||||
|
title: 'Page 1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = findFirstPageRoute(routes);
|
||||||
|
expect(result).toEqual(routes[0].children[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试:多层嵌套路由
|
||||||
|
it('should find page route in deeply nested groups', () => {
|
||||||
|
const routes: NocoBaseDesktopRoute[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: NocoBaseDesktopRouteType.group,
|
||||||
|
title: 'Group 1',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
type: NocoBaseDesktopRouteType.group,
|
||||||
|
title: 'Group 1-1',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 111,
|
||||||
|
schemaUid: 'page1',
|
||||||
|
type: NocoBaseDesktopRouteType.page,
|
||||||
|
title: 'Page 1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = findFirstPageRoute(routes);
|
||||||
|
expect(result).toEqual(routes[0].children[0].children[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试:复杂路由结构
|
||||||
|
it('should find the first visible page in a complex route structure', () => {
|
||||||
|
const routes: NocoBaseDesktopRoute[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: NocoBaseDesktopRouteType.group,
|
||||||
|
title: 'Group 1',
|
||||||
|
hideInMenu: true,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 11,
|
||||||
|
schemaUid: 'page1',
|
||||||
|
type: NocoBaseDesktopRouteType.page,
|
||||||
|
title: 'Page 1',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
type: NocoBaseDesktopRouteType.group,
|
||||||
|
title: 'Group 2',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: 21,
|
||||||
|
schemaUid: 'page2',
|
||||||
|
type: NocoBaseDesktopRouteType.page,
|
||||||
|
title: 'Page 2',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = findFirstPageRoute(routes);
|
||||||
|
expect(result).toEqual(routes[1].children[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 测试:空组
|
||||||
|
it('should skip empty groups and find page in next group', () => {
|
||||||
|
const routes: NocoBaseDesktopRoute[] = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
type: NocoBaseDesktopRouteType.group,
|
||||||
|
title: 'Empty Group',
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
schemaUid: 'page1',
|
||||||
|
type: NocoBaseDesktopRouteType.page,
|
||||||
|
title: 'Page 1',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = findFirstPageRoute(routes);
|
||||||
|
expect(result).toEqual(routes[1]);
|
||||||
|
});
|
||||||
|
});
|
@ -611,27 +611,11 @@ export const InternalAdminLayout = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function getDefaultPageUid(routes: NocoBaseDesktopRoute[]) {
|
|
||||||
// Find the first route of type "page"
|
|
||||||
for (const route of routes) {
|
|
||||||
if (route.type === NocoBaseDesktopRouteType.page) {
|
|
||||||
return route.schemaUid;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (route.children?.length) {
|
|
||||||
const result = getDefaultPageUid(route.children);
|
|
||||||
if (result) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const NavigateToDefaultPage: FC = (props) => {
|
const NavigateToDefaultPage: FC = (props) => {
|
||||||
const { allAccessRoutes } = useAllAccessDesktopRoutes();
|
const { allAccessRoutes } = useAllAccessDesktopRoutes();
|
||||||
const location = useLocationNoUpdate();
|
const location = useLocationNoUpdate();
|
||||||
|
|
||||||
const defaultPageUid = getDefaultPageUid(allAccessRoutes);
|
const defaultPageUid = findFirstPageRoute(allAccessRoutes)?.schemaUid;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -741,36 +725,6 @@ export function findRouteBySchemaUid(schemaUid: string, treeArray: any[]) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MenuItemIcon: FC<{ icon: string; title: string }> = (props) => {
|
|
||||||
const { inHeader } = useContext(headerContext);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<RouteContext.Consumer>
|
|
||||||
{(value: RouteContextType) => {
|
|
||||||
const { collapsed } = value;
|
|
||||||
|
|
||||||
if (collapsed && !inHeader) {
|
|
||||||
return props.icon ? (
|
|
||||||
<Icon type={props.icon} />
|
|
||||||
) : (
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
display: 'inline-block',
|
|
||||||
width: '100%',
|
|
||||||
textAlign: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{props.title.charAt(0)}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return props.icon ? <Icon type={props.icon} /> : null;
|
|
||||||
}}
|
|
||||||
</RouteContext.Consumer>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const MenuDesignerButton: FC<{ testId: string }> = (props) => {
|
const MenuDesignerButton: FC<{ testId: string }> = (props) => {
|
||||||
const { render: renderInitializer } = useSchemaInitializerRender(menuItemInitializer);
|
const { render: renderInitializer } = useSchemaInitializerRender(menuItemInitializer);
|
||||||
|
|
||||||
@ -892,16 +846,17 @@ function findRouteById(id: string, treeArray: any[]) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findFirstPageRoute(routes: NocoBaseDesktopRoute[]) {
|
export function findFirstPageRoute(routes: NocoBaseDesktopRoute[]) {
|
||||||
if (!routes) return;
|
if (!routes) return;
|
||||||
|
|
||||||
for (const route of routes) {
|
for (const route of routes.filter((item) => !item.hideInMenu)) {
|
||||||
if (route.type === NocoBaseDesktopRouteType.page) {
|
if (route.type === NocoBaseDesktopRouteType.page) {
|
||||||
return route;
|
return route;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (route.children?.length) {
|
if (route.type === NocoBaseDesktopRouteType.group && route.children?.length) {
|
||||||
return findFirstPageRoute(route.children);
|
const result = findFirstPageRoute(route.children);
|
||||||
|
if (result) return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,21 +67,24 @@ const ActionModalContent: FC<{ footerNodeName: string; field: any; schema: any }
|
|||||||
);
|
);
|
||||||
|
|
||||||
export function useDelayedVisible(visible: boolean, delay = 200) {
|
export function useDelayedVisible(visible: boolean, delay = 200) {
|
||||||
const [ready, setReady] = useState(false);
|
const [ready, setReady] = useState(delay === 0);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (ready) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (visible) {
|
if (visible) {
|
||||||
const timer = setTimeout(() => setReady(true), delay);
|
const timer = setTimeout(() => setReady(true), delay);
|
||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
} else {
|
} else {
|
||||||
setReady(false);
|
setReady(false);
|
||||||
}
|
}
|
||||||
}, [visible]);
|
}, [delay, ready, visible]);
|
||||||
return ready;
|
return ready;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const InternalActionModal: React.FC<ActionDrawerProps<ModalProps>> = observer(
|
export const InternalActionModal: React.FC<ActionDrawerProps<ModalProps>> = observer(
|
||||||
(props) => {
|
(props) => {
|
||||||
const { footerNodeName = 'Action.Modal.Footer', width, zIndex: _zIndex, ...others } = props;
|
const { footerNodeName = 'Action.Modal.Footer', width, zIndex: _zIndex, delay = 200, ...others } = props;
|
||||||
const { visible, setVisible, openSize = 'middle', modalProps } = useActionContext();
|
const { visible, setVisible, openSize = 'middle', modalProps } = useActionContext();
|
||||||
const actualWidth = width ?? openSizeWidthMap.get(openSize);
|
const actualWidth = width ?? openSizeWidthMap.get(openSize);
|
||||||
const schema = useFieldSchema();
|
const schema = useFieldSchema();
|
||||||
@ -102,7 +105,7 @@ export const InternalActionModal: React.FC<ActionDrawerProps<ModalProps>> = obse
|
|||||||
}
|
}
|
||||||
|
|
||||||
const zIndex = getZIndex('modal', _zIndex || parentZIndex, props.level || 0);
|
const zIndex = getZIndex('modal', _zIndex || parentZIndex, props.level || 0);
|
||||||
const ready = useDelayedVisible(visible, 200); // 200ms 与 Modal 动画时间一致
|
const ready = useDelayedVisible(visible, delay); // 200ms 与 Modal 动画时间一致
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionContextNoRerender>
|
<ActionContextNoRerender>
|
||||||
|
@ -321,11 +321,7 @@ const InternalAction: React.FC<InternalActionProps> = observer(function Com(prop
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (addChild) {
|
if (addChild) {
|
||||||
return wrapSSR(
|
return wrapSSR(<TreeRecordProvider parent={recordData}>{result}</TreeRecordProvider>) as React.ReactElement;
|
||||||
<RecordProvider record={null} parent={parentRecordData}>
|
|
||||||
<TreeRecordProvider parent={recordData}>{result}</TreeRecordProvider>
|
|
||||||
</RecordProvider>,
|
|
||||||
) as React.ReactElement;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return wrapSSR(result) as React.ReactElement;
|
return wrapSSR(result) as React.ReactElement;
|
||||||
|
@ -92,6 +92,7 @@ export type ActionDrawerProps<T = DrawerProps> = T & {
|
|||||||
footerNodeName?: string;
|
footerNodeName?: string;
|
||||||
/** 当前弹窗嵌套的层级 */
|
/** 当前弹窗嵌套的层级 */
|
||||||
level?: number;
|
level?: number;
|
||||||
|
delay?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ComposedActionDrawer<T = DrawerProps> = React.FC<ActionDrawerProps<T>> & {
|
export type ComposedActionDrawer<T = DrawerProps> = React.FC<ActionDrawerProps<T>> & {
|
||||||
|
@ -34,7 +34,7 @@ import useServiceOptions, { useAssociationFieldContext } from './hooks';
|
|||||||
|
|
||||||
const removeIfKeyEmpty = (obj, filterTargetKey) => {
|
const removeIfKeyEmpty = (obj, filterTargetKey) => {
|
||||||
if (!obj || typeof obj !== 'object' || !filterTargetKey || Array.isArray(obj)) return obj;
|
if (!obj || typeof obj !== 'object' || !filterTargetKey || Array.isArray(obj)) return obj;
|
||||||
return !obj[filterTargetKey] ? null : obj;
|
return !obj[filterTargetKey] ? undefined : obj;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const AssociationFieldAddNewer = (props) => {
|
export const AssociationFieldAddNewer = (props) => {
|
||||||
@ -106,8 +106,13 @@ const InternalAssociationSelect = observer(
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initValue = isVariable(field.value) ? undefined : field.value;
|
const initValue = isVariable(field.value) ? undefined : field.value;
|
||||||
const value = Array.isArray(initValue) ? initValue.filter(Boolean) : initValue;
|
const value = Array.isArray(initValue) ? initValue.filter(Boolean) : initValue;
|
||||||
setInnerValue(value);
|
const result = removeIfKeyEmpty(value, filterTargetKey);
|
||||||
}, [field.value]);
|
setInnerValue(result);
|
||||||
|
if (!isEqual(field.value, result)) {
|
||||||
|
field.value = result;
|
||||||
|
}
|
||||||
|
}, [field.value, filterTargetKey]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const id = uid();
|
const id = uid();
|
||||||
form.addEffects(id, () => {
|
form.addEffects(id, () => {
|
||||||
|
@ -54,7 +54,7 @@ describe('CollectionSelect', () => {
|
|||||||
role="button"
|
role="button"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="css-9mlexe ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-11aiz3o"
|
class="css-a7w9kk ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-11aiz3o"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ant-formily-item-label"
|
class="ant-formily-item-label"
|
||||||
@ -191,7 +191,7 @@ describe('CollectionSelect', () => {
|
|||||||
role="button"
|
role="button"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="css-9mlexe ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-11aiz3o"
|
class="css-a7w9kk ant-formily-item ant-formily-item-layout-horizontal ant-formily-item-feedback-layout-loose ant-formily-item-label-align-right ant-formily-item-control-align-left css-dev-only-do-not-override-11aiz3o"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="ant-formily-item-label"
|
class="ant-formily-item-label"
|
||||||
|
@ -268,8 +268,9 @@ DatePicker.FilterWithPicker = function FilterWithPicker(props: any) {
|
|||||||
const value = Array.isArray(props.value) ? props.value[0] : props.value;
|
const value = Array.isArray(props.value) ? props.value[0] : props.value;
|
||||||
const compile = useCompile();
|
const compile = useCompile();
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const targetPicker = value ? inferPickerType(value, picker) : picker;
|
const initPicker = value ? inferPickerType(value, picker) : picker;
|
||||||
const targetDateFormat = getPickerFormat(targetPicker) || format;
|
const [targetPicker, setTargetPicker] = useState(initPicker);
|
||||||
|
const targetDateFormat = getPickerFormat(initPicker) || format;
|
||||||
const newProps = {
|
const newProps = {
|
||||||
utc,
|
utc,
|
||||||
inputReadOnly: isMobileMedia,
|
inputReadOnly: isMobileMedia,
|
||||||
@ -287,12 +288,6 @@ DatePicker.FilterWithPicker = function FilterWithPicker(props: any) {
|
|||||||
};
|
};
|
||||||
const field: any = useField();
|
const field: any = useField();
|
||||||
const [stateProps, setStateProps] = useState(newProps);
|
const [stateProps, setStateProps] = useState(newProps);
|
||||||
useEffect(() => {
|
|
||||||
newProps.picker = targetPicker;
|
|
||||||
const dateTimeFormat = getDateTimeFormat(targetPicker, targetDateFormat, showTime, timeFormat);
|
|
||||||
newProps.format = dateTimeFormat;
|
|
||||||
setStateProps(newProps);
|
|
||||||
}, [targetPicker]);
|
|
||||||
return (
|
return (
|
||||||
<Space.Compact style={{ width: '100%' }}>
|
<Space.Compact style={{ width: '100%' }}>
|
||||||
<Select
|
<Select
|
||||||
@ -322,6 +317,7 @@ DatePicker.FilterWithPicker = function FilterWithPicker(props: any) {
|
|||||||
},
|
},
|
||||||
])}
|
])}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
|
setTargetPicker(value);
|
||||||
const format = getPickerFormat(value);
|
const format = getPickerFormat(value);
|
||||||
const dateTimeFormat = getDateTimeFormat(value, format, showTime, timeFormat);
|
const dateTimeFormat = getDateTimeFormat(value, format, showTime, timeFormat);
|
||||||
field.setComponentProps({
|
field.setComponentProps({
|
||||||
|
@ -37,15 +37,17 @@ const formItemWrapCss = css`
|
|||||||
.ant-description-textarea img {
|
.ant-description-textarea img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
&.ant-formily-item-layout-vertical .ant-formily-item-label {
|
&.ant-formily-item-layout-horizontal.ant-formily-item-label-wrap {
|
||||||
|
.ant-formily-item-label {
|
||||||
display: inline;
|
display: inline;
|
||||||
.ant-formily-item-label-tooltip-icon {
|
padding-right: 5px;
|
||||||
display: inline;
|
|
||||||
}
|
.ant-formily-item-label-tooltip-icon,
|
||||||
.ant-formily-item-label-content {
|
.ant-formily-item-label-content {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const formItemLabelCss = css`
|
const formItemLabelCss = css`
|
||||||
|
@ -101,6 +101,10 @@ const useLazyLoadDisplayAssociationFieldsOfForm = () => {
|
|||||||
field.value = null;
|
field.value = null;
|
||||||
} else {
|
} else {
|
||||||
field.value = result;
|
field.value = result;
|
||||||
|
field.componentProps = {
|
||||||
|
...field.componentProps,
|
||||||
|
readOnlySubmit: true,
|
||||||
|
}; // 让它不参与提交
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -16,8 +16,10 @@ import { ConfigProvider, theme } from 'antd';
|
|||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo } from 'react';
|
||||||
import { useActionContext } from '..';
|
import { useActionContext } from '..';
|
||||||
import { useAttach, useComponent } from '../..';
|
import { useAttach, useComponent } from '../..';
|
||||||
|
import { useApp } from '../../../application';
|
||||||
import { getCardItemSchema } from '../../../block-provider';
|
import { getCardItemSchema } from '../../../block-provider';
|
||||||
import { useTemplateBlockContext } from '../../../block-provider/TemplateBlockProvider';
|
import { useTemplateBlockContext } from '../../../block-provider/TemplateBlockProvider';
|
||||||
|
import { useDataBlockProps } from '../../../data-source';
|
||||||
import { useDataBlockRequest } from '../../../data-source/data-block/DataBlockRequestProvider';
|
import { useDataBlockRequest } from '../../../data-source/data-block/DataBlockRequestProvider';
|
||||||
import { NocoBaseRecursionField } from '../../../formily/NocoBaseRecursionField';
|
import { NocoBaseRecursionField } from '../../../formily/NocoBaseRecursionField';
|
||||||
import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps';
|
import { withDynamicSchemaProps } from '../../../hoc/withDynamicSchemaProps';
|
||||||
@ -27,7 +29,6 @@ import { useToken } from '../../../style';
|
|||||||
import { useLocalVariables, useVariables } from '../../../variables';
|
import { useLocalVariables, useVariables } from '../../../variables';
|
||||||
import { useProps } from '../../hooks/useProps';
|
import { useProps } from '../../hooks/useProps';
|
||||||
import { useFormBlockHeight } from './hook';
|
import { useFormBlockHeight } from './hook';
|
||||||
import { useApp } from '../../../application';
|
|
||||||
|
|
||||||
export interface FormProps extends IFormLayoutProps {
|
export interface FormProps extends IFormLayoutProps {
|
||||||
form?: FormilyForm;
|
form?: FormilyForm;
|
||||||
@ -141,12 +142,15 @@ const WithForm = (props: WithFormProps) => {
|
|||||||
const linkageRules: any[] =
|
const linkageRules: any[] =
|
||||||
(getLinkageRules(fieldSchema) || fieldSchema.parent?.['x-linkage-rules'])?.filter((k) => !k.disabled) || [];
|
(getLinkageRules(fieldSchema) || fieldSchema.parent?.['x-linkage-rules'])?.filter((k) => !k.disabled) || [];
|
||||||
|
|
||||||
|
// 关闭弹窗之前,如果有未保存的数据,是否要二次确认
|
||||||
|
const { confirmBeforeClose = true } = useDataBlockProps() || ({} as any);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const id = uid();
|
const id = uid();
|
||||||
|
|
||||||
form.addEffects(id, () => {
|
form.addEffects(id, () => {
|
||||||
onFormInputChange(() => {
|
onFormInputChange(() => {
|
||||||
setFormValueChanged?.(true);
|
setFormValueChanged?.(confirmBeforeClose);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -157,7 +161,7 @@ const WithForm = (props: WithFormProps) => {
|
|||||||
return () => {
|
return () => {
|
||||||
form.removeEffects(id);
|
form.removeEffects(id);
|
||||||
};
|
};
|
||||||
}, [form, props.disabled, setFormValueChanged]);
|
}, [form, props.disabled, setFormValueChanged, confirmBeforeClose]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@ -210,17 +214,19 @@ const WithForm = (props: WithFormProps) => {
|
|||||||
const WithoutForm = (props) => {
|
const WithoutForm = (props) => {
|
||||||
const fieldSchema = useFieldSchema();
|
const fieldSchema = useFieldSchema();
|
||||||
const { setFormValueChanged } = useActionContext();
|
const { setFormValueChanged } = useActionContext();
|
||||||
|
// 关闭弹窗之前,如果有未保存的数据,是否要二次确认
|
||||||
|
const { confirmBeforeClose = true } = useDataBlockProps() || ({} as any);
|
||||||
const form = useMemo(
|
const form = useMemo(
|
||||||
() =>
|
() =>
|
||||||
createForm({
|
createForm({
|
||||||
disabled: props.disabled,
|
disabled: props.disabled,
|
||||||
effects() {
|
effects() {
|
||||||
onFormInputChange((form) => {
|
onFormInputChange((form) => {
|
||||||
setFormValueChanged?.(true);
|
setFormValueChanged?.(confirmBeforeClose);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[],
|
[confirmBeforeClose],
|
||||||
);
|
);
|
||||||
return fieldSchema['x-decorator'] === 'FormV2' ? (
|
return fieldSchema['x-decorator'] === 'FormV2' ? (
|
||||||
<FormDecorator form={form} {...props} />
|
<FormDecorator form={form} {...props} />
|
||||||
|
@ -42,6 +42,8 @@ function InputInner(props: NocoBaseInputProps) {
|
|||||||
return <AntdInput {...others} onChange={handleChange} />;
|
return <AntdInput {...others} onChange={handleChange} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InputInner.Password = AntdInput.Password;
|
||||||
|
|
||||||
export const Input: ComposedInput = Object.assign(
|
export const Input: ComposedInput = Object.assign(
|
||||||
connect(
|
connect(
|
||||||
InputInner,
|
InputInner,
|
||||||
|
@ -393,10 +393,11 @@ export function Uploader({ rules, ...props }: UploadProps) {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (pendingList.length) {
|
if (pendingList.length) {
|
||||||
|
const errorFiles = pendingList.filter((item) => item.status === 'error');
|
||||||
field.setFeedback({
|
field.setFeedback({
|
||||||
type: 'error',
|
type: 'error',
|
||||||
code: 'ValidateError',
|
code: 'ValidateError',
|
||||||
messages: [t('Incomplete uploading files need to be resolved')],
|
messages: [errorFiles.length ? t('Some files are not uploaded correctly, please check.') : ' '],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
field.setFeedback({});
|
field.setFeedback({});
|
||||||
|
@ -7,9 +7,9 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useRef, useState } from 'react';
|
|
||||||
import { css } from '@emotion/css';
|
import { css } from '@emotion/css';
|
||||||
import { Button, Input } from 'antd';
|
import { Button, Input } from 'antd';
|
||||||
|
import React, { useRef, useState } from 'react';
|
||||||
import { VariableSelect } from './VariableSelect';
|
import { VariableSelect } from './VariableSelect';
|
||||||
|
|
||||||
// NOTE: https://stackoverflow.com/questions/23892547/what-is-the-best-way-to-trigger-onchange-event-in-react-js/46012210#46012210
|
// NOTE: https://stackoverflow.com/questions/23892547/what-is-the-best-way-to-trigger-onchange-event-in-react-js/46012210#46012210
|
||||||
@ -80,6 +80,7 @@ export function RawTextArea(props): JSX.Element {
|
|||||||
setOptions={setOptions}
|
setOptions={setOptions}
|
||||||
onInsert={onInsert}
|
onInsert={onInsert}
|
||||||
changeOnSelect={changeOnSelect}
|
changeOnSelect={changeOnSelect}
|
||||||
|
disabled={others.disabled}
|
||||||
/>
|
/>
|
||||||
</Button.Group>
|
</Button.Group>
|
||||||
</div>
|
</div>
|
||||||
|
@ -796,7 +796,7 @@ export function useDesignable() {
|
|||||||
return component;
|
return component;
|
||||||
}
|
}
|
||||||
const c = get(components, component);
|
const c = get(components, component);
|
||||||
return c[LAZY_COMPONENT_KEY] ?? c;
|
return c?.[LAZY_COMPONENT_KEY] ?? c;
|
||||||
},
|
},
|
||||||
[get],
|
[get],
|
||||||
),
|
),
|
||||||
|
@ -31,12 +31,7 @@ export const useFieldModeOptions = (props?) => {
|
|||||||
if (!collectionField || !collectionField?.interface) {
|
if (!collectionField || !collectionField?.interface) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (
|
if (!['hasOne', 'hasMany', 'belongsTo', 'belongsToMany', 'belongsToArray'].includes(collectionField.type)) return;
|
||||||
!['o2o', 'oho', 'obo', 'o2m', 'linkTo', 'm2o', 'm2m', 'updatedBy', 'createdBy', 'mbm', 'attachmentURL'].includes(
|
|
||||||
collectionField.interface,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
const collection = getCollection(collectionField.target);
|
const collection = getCollection(collectionField.target);
|
||||||
if (collection?.template === 'file') {
|
if (collection?.template === 'file') {
|
||||||
return isReadPretty
|
return isReadPretty
|
||||||
|
@ -727,7 +727,7 @@ export const useCustomFormItemInitializerFields = (options?: any) => {
|
|||||||
const remove = useRemoveGridFormItem();
|
const remove = useRemoveGridFormItem();
|
||||||
return currentFields
|
return currentFields
|
||||||
?.filter((field) => {
|
?.filter((field) => {
|
||||||
return field?.interface && field.interface !== 'snapshot' && field.type !== 'sequence';
|
return !field.inherit && field?.interface && field.interface !== 'snapshot' && field.type !== 'sequence';
|
||||||
})
|
})
|
||||||
?.map((field) => {
|
?.map((field) => {
|
||||||
const interfaceConfig = getInterface(field.interface);
|
const interfaceConfig = getInterface(field.interface);
|
||||||
@ -767,7 +767,7 @@ export const findSchema = (schema: Schema, key: string, action: string, name?: s
|
|||||||
if (s[key] === action && (!name || s.name === name)) {
|
if (s[key] === action && (!name || s.name === name)) {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
if (s['x-component'] !== 'Action.Container' && !s['x-component'].includes('AssociationField')) {
|
if (s['x-component'] && s['x-component'] !== 'Action.Container' && !s['x-component'].includes('AssociationField')) {
|
||||||
const c = findSchema(s, key, action, name);
|
const c = findSchema(s, key, action, name);
|
||||||
if (c) {
|
if (c) {
|
||||||
return c;
|
return c;
|
||||||
|
@ -339,11 +339,6 @@ const InternalSchemaToolbar: FC<SchemaToolbarProps> = React.memo((props) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// const style = window.getComputedStyle(parentElement);
|
|
||||||
// if (style.position === 'static') {
|
|
||||||
// parentElement.style.position = 'relative';
|
|
||||||
// }
|
|
||||||
|
|
||||||
el.addEventListener('mouseenter', show);
|
el.addEventListener('mouseenter', show);
|
||||||
el.addEventListener('mouseleave', hide);
|
el.addEventListener('mouseleave', hide);
|
||||||
return () => {
|
return () => {
|
||||||
|
@ -97,6 +97,17 @@ export const ValueDynamicComponent = (props: ValueDynamicComponentProps) => {
|
|||||||
.ant-input-affix-wrapper {
|
.ant-input-affix-wrapper {
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
}
|
}
|
||||||
|
.ant-checkbox-wrapper {
|
||||||
|
margin-left: 50%;
|
||||||
|
}
|
||||||
|
.ant-select-selector {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
.ant-picker {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{React.createElement(DynamicComponent, {
|
{React.createElement(DynamicComponent, {
|
||||||
@ -120,7 +131,7 @@ export const ValueDynamicComponent = (props: ValueDynamicComponentProps) => {
|
|||||||
<span style={{ marginLeft: '.25em' }} className={'ant-formily-item-extra'}>
|
<span style={{ marginLeft: '.25em' }} className={'ant-formily-item-extra'}>
|
||||||
{t('Syntax references')}:
|
{t('Syntax references')}:
|
||||||
</span>
|
</span>
|
||||||
<a href="https://formulajs.info/functions/" target="_blank" rel="noreferrer">
|
<a href="https://docs.nocobase.com/handbook/calculation-engines/formula" target="_blank" rel="noreferrer">
|
||||||
Formula.js
|
Formula.js
|
||||||
</a>
|
</a>
|
||||||
</>
|
</>
|
||||||
|
@ -247,6 +247,13 @@ function getSubscriber(
|
|||||||
// 在 FormItem 中有使用这个属性来判断字段是否被隐藏
|
// 在 FormItem 中有使用这个属性来判断字段是否被隐藏
|
||||||
field.data.hidden = true;
|
field.data.hidden = true;
|
||||||
|
|
||||||
|
// 如果字段是必填的,并且被隐藏(保留值)了,那么就需要把 required 设置为 false,否则有可能会导致表单验证失败;
|
||||||
|
// 进而导致点击提交按钮无效的问题。
|
||||||
|
if (field.required) {
|
||||||
|
field.required = false;
|
||||||
|
field.data.prevRequired = true;
|
||||||
|
}
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
field.setState((state) => {
|
field.setState((state) => {
|
||||||
state.display = 'visible';
|
state.display = 'visible';
|
||||||
@ -257,6 +264,13 @@ function getSubscriber(
|
|||||||
field.data = field.data || {};
|
field.data = field.data || {};
|
||||||
// 在 FormItem 中有使用这个属性来判断字段是否被隐藏
|
// 在 FormItem 中有使用这个属性来判断字段是否被隐藏
|
||||||
field.data.hidden = false;
|
field.data.hidden = false;
|
||||||
|
|
||||||
|
// 当“隐藏(保留值)”的字段再次显示时,恢复“必填”的状态
|
||||||
|
if (fieldName === 'display' && lastState?.value === 'visible' && field.data.prevRequired) {
|
||||||
|
delete field.data.prevRequired;
|
||||||
|
field.required = true;
|
||||||
|
}
|
||||||
|
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
field.setState((state) => {
|
field.setState((state) => {
|
||||||
state[fieldName] = lastState?.value;
|
state[fieldName] = lastState?.value;
|
||||||
|
@ -33,7 +33,12 @@ export const useValues = (options) => {
|
|||||||
const dataIndex = field.data?.targetFields;
|
const dataIndex = field.data?.targetFields;
|
||||||
const option = (dataIndex && findOption(dataIndex, options)) || {};
|
const option = (dataIndex && findOption(dataIndex, options)) || {};
|
||||||
const operators = option?.operators || [];
|
const operators = option?.operators || [];
|
||||||
field.data.operators = operators;
|
field.data.operators = operators?.filter((v) => {
|
||||||
|
if (dataIndex.length > 1) {
|
||||||
|
return v.value !== 'value';
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
field.data.schema = option?.schema;
|
field.data.schema = option?.schema;
|
||||||
};
|
};
|
||||||
useEffect(value2data, [logic]);
|
useEffect(value2data, [logic]);
|
||||||
|
@ -48,6 +48,7 @@ import {
|
|||||||
SchemaSettingsItemType,
|
SchemaSettingsItemType,
|
||||||
SchemaToolbarVisibleContext,
|
SchemaToolbarVisibleContext,
|
||||||
VariablesContext,
|
VariablesContext,
|
||||||
|
getZIndex,
|
||||||
useCollection,
|
useCollection,
|
||||||
useCollectionManager,
|
useCollectionManager,
|
||||||
useZIndexContext,
|
useZIndexContext,
|
||||||
@ -695,7 +696,7 @@ export const SchemaSettingsActionModalItem: FC<SchemaSettingsActionModalItemProp
|
|||||||
const upLevelActiveFields = useFormActiveFields();
|
const upLevelActiveFields = useFormActiveFields();
|
||||||
const parentZIndex = useZIndexContext();
|
const parentZIndex = useZIndexContext();
|
||||||
|
|
||||||
const zIndex = parentZIndex + 10;
|
const zIndex = getZIndex('modal', parentZIndex + 10, 0);
|
||||||
|
|
||||||
const form = useMemo(
|
const form = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
import { expect, test } from '@nocobase/test/e2e';
|
import { expect, test } from '@nocobase/test/e2e';
|
||||||
import {
|
import {
|
||||||
formFieldDependsOnSubtableFieldsWithLinkageRules,
|
formFieldDependsOnSubtableFieldsWithLinkageRules,
|
||||||
|
whenARequiredFieldIsSetToHideRetainValueItShouldBeAbleToSubmitTheFormNormally,
|
||||||
whenClearingARelationshipFieldTheValueOfTheAssociatedFieldShouldBeCleared,
|
whenClearingARelationshipFieldTheValueOfTheAssociatedFieldShouldBeCleared,
|
||||||
whenSetToHideRetainedValueItShouldNotImpactTheFieldSDefaultValueVariables,
|
whenSetToHideRetainedValueItShouldNotImpactTheFieldSDefaultValueVariables,
|
||||||
} from './template';
|
} from './template';
|
||||||
@ -83,6 +84,28 @@ test.describe('linkage rules', () => {
|
|||||||
).toBeVisible();
|
).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('When a required field is set to "Hide (retain value)", it should be able to submit the form normally', async ({
|
||||||
|
mockPage,
|
||||||
|
page,
|
||||||
|
}) => {
|
||||||
|
await mockPage(whenARequiredFieldIsSetToHideRetainValueItShouldBeAbleToSubmitTheFormNormally).goto();
|
||||||
|
|
||||||
|
// 1. 输入昵称
|
||||||
|
await page
|
||||||
|
.getByLabel('block-item-CollectionField-users-form-users.nickname-Nickname')
|
||||||
|
.getByRole('textbox')
|
||||||
|
.fill('123456');
|
||||||
|
|
||||||
|
// 2. 点击提交
|
||||||
|
await page.getByLabel('action-Action-Submit-submit-').click();
|
||||||
|
|
||||||
|
// 3. 应该能正常提交,不应该被拦截
|
||||||
|
await page.reload();
|
||||||
|
await expect(
|
||||||
|
page.getByLabel('block-item-CardItem-users-table').getByRole('cell', { name: '123456' }),
|
||||||
|
).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
test('When clearing a relationship field, the value of the associated field should be cleared', async ({
|
test('When clearing a relationship field, the value of the associated field should be cleared', async ({
|
||||||
page,
|
page,
|
||||||
mockPage,
|
mockPage,
|
||||||
|
@ -2215,3 +2215,376 @@ export const accessControlActionWithTable = {
|
|||||||
'x-index': 1,
|
'x-index': 1,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
export const whenARequiredFieldIsSetToHideRetainValueItShouldBeAbleToSubmitTheFormNormally = {
|
||||||
|
tabSchema: {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-initializer': 'page:addBlock',
|
||||||
|
properties: {
|
||||||
|
phefo1qp4yu: {
|
||||||
|
type: 'void',
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'Grid.Row',
|
||||||
|
'x-app-version': '1.6.21',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
properties: {
|
||||||
|
e94p5oj6num: {
|
||||||
|
type: 'void',
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'Grid.Col',
|
||||||
|
'x-app-version': '1.6.21',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
properties: {
|
||||||
|
fpwdszedqqt: {
|
||||||
|
type: 'void',
|
||||||
|
version: '2.0',
|
||||||
|
'x-toolbar': 'BlockSchemaToolbar',
|
||||||
|
'x-settings': 'blockSettings:createForm',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
'x-decorator': 'FormBlockProvider',
|
||||||
|
'x-acl-action': 'users:create',
|
||||||
|
'x-app-version': '1.6.21',
|
||||||
|
'x-decorator-props': {
|
||||||
|
collection: 'users',
|
||||||
|
dataSource: 'main',
|
||||||
|
},
|
||||||
|
'x-acl-action-props': {
|
||||||
|
skipScopeCheck: true,
|
||||||
|
},
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
'x-use-decorator-props': 'useCreateFormBlockDecoratorProps',
|
||||||
|
properties: {
|
||||||
|
fy0innr5s9v: {
|
||||||
|
type: 'void',
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'FormV2',
|
||||||
|
'x-app-version': '1.6.21',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
'x-use-component-props': 'useCreateFormBlockProps',
|
||||||
|
properties: {
|
||||||
|
grid: {
|
||||||
|
type: 'void',
|
||||||
|
'x-uid': '7wwn4d0722h',
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'Grid',
|
||||||
|
'x-app-version': '1.6.21',
|
||||||
|
'x-initializer': 'form:configureFields',
|
||||||
|
'x-linkage-rules': [
|
||||||
|
{
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
operator: 'hidden',
|
||||||
|
targetFields: ['username'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
condition: {
|
||||||
|
$and: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
properties: {
|
||||||
|
t9ik3qkj7mv: {
|
||||||
|
type: 'void',
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'Grid.Row',
|
||||||
|
'x-app-version': '1.6.21',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
properties: {
|
||||||
|
szfeenblh7q: {
|
||||||
|
type: 'void',
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'Grid.Col',
|
||||||
|
'x-app-version': '1.6.21',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
properties: {
|
||||||
|
username: {
|
||||||
|
type: 'string',
|
||||||
|
version: '2.0',
|
||||||
|
'x-toolbar': 'FormItemSchemaToolbar',
|
||||||
|
'x-settings': 'fieldSettings:FormItem',
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-app-version': '1.6.21',
|
||||||
|
'x-component-props': {},
|
||||||
|
'x-collection-field': 'users.username',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
'x-uid': 'w7j6pbapyz2',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '4p4e653i4sg',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'ojqn0csar3b',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
ij66m020mn5: {
|
||||||
|
type: 'void',
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'Grid.Row',
|
||||||
|
'x-app-version': '1.6.21',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
properties: {
|
||||||
|
'95nih4mt2lf': {
|
||||||
|
type: 'void',
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'Grid.Col',
|
||||||
|
'x-app-version': '1.6.21',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
properties: {
|
||||||
|
nickname: {
|
||||||
|
type: 'string',
|
||||||
|
version: '2.0',
|
||||||
|
'x-toolbar': 'FormItemSchemaToolbar',
|
||||||
|
'x-settings': 'fieldSettings:FormItem',
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-decorator': 'FormItem',
|
||||||
|
'x-app-version': '1.6.21',
|
||||||
|
'x-component-props': {},
|
||||||
|
'x-collection-field': 'users.nickname',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
'x-uid': 'rpmd8swoor7',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'h0z7u62fwn3',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'b3v741bdvtt',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
o5pser9a4zy: {
|
||||||
|
type: 'void',
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-app-version': '1.6.21',
|
||||||
|
'x-initializer': 'createForm:configureActions',
|
||||||
|
'x-component-props': {
|
||||||
|
layout: 'one-column',
|
||||||
|
},
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
properties: {
|
||||||
|
g5aat11143b: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Submit") }}',
|
||||||
|
version: '2.0',
|
||||||
|
'x-action': 'submit',
|
||||||
|
'x-toolbar': 'ActionSchemaToolbar',
|
||||||
|
'x-settings': 'actionSettings:createSubmit',
|
||||||
|
'x-component': 'Action',
|
||||||
|
'x-app-version': '1.6.21',
|
||||||
|
'x-action-settings': {},
|
||||||
|
'x-component-props': {
|
||||||
|
type: 'primary',
|
||||||
|
htmlType: 'submit',
|
||||||
|
},
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
'x-use-component-props': 'useCreateActionProps',
|
||||||
|
'x-uid': 'doarbo27x5f',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'n5j6q3dfzst',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '1k05k8tv3fp',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '4bq4fts48ec',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'tgjtas03gh6',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'raas2rrgtgq',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
gqnjk39afuj: {
|
||||||
|
type: 'void',
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'Grid.Row',
|
||||||
|
'x-app-version': '1.6.21',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
properties: {
|
||||||
|
fvn883pa5lt: {
|
||||||
|
type: 'void',
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'Grid.Col',
|
||||||
|
'x-app-version': '1.6.21',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
properties: {
|
||||||
|
'0wlnuaz623s': {
|
||||||
|
type: 'void',
|
||||||
|
version: '2.0',
|
||||||
|
'x-toolbar': 'BlockSchemaToolbar',
|
||||||
|
'x-settings': 'blockSettings:table',
|
||||||
|
'x-component': 'CardItem',
|
||||||
|
'x-decorator': 'TableBlockProvider',
|
||||||
|
'x-acl-action': 'users:list',
|
||||||
|
'x-app-version': '1.6.21',
|
||||||
|
'x-filter-targets': [],
|
||||||
|
'x-decorator-props': {
|
||||||
|
action: 'list',
|
||||||
|
params: {
|
||||||
|
pageSize: 20,
|
||||||
|
},
|
||||||
|
rowKey: 'id',
|
||||||
|
dragSort: false,
|
||||||
|
showIndex: true,
|
||||||
|
collection: 'users',
|
||||||
|
dataSource: 'main',
|
||||||
|
},
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
'x-use-decorator-props': 'useTableBlockDecoratorProps',
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
type: 'void',
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'ActionBar',
|
||||||
|
'x-app-version': '1.6.21',
|
||||||
|
'x-initializer': 'table:configureActions',
|
||||||
|
'x-component-props': {
|
||||||
|
style: {
|
||||||
|
marginBottom: 'var(--nb-spacing)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
'x-uid': 'q7y8z49vkvs',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
e7dmvrnhhhw: {
|
||||||
|
type: 'array',
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'TableV2',
|
||||||
|
'x-app-version': '1.6.21',
|
||||||
|
'x-initializer': 'table:configureColumns',
|
||||||
|
'x-component-props': {
|
||||||
|
rowKey: 'id',
|
||||||
|
rowSelection: {
|
||||||
|
type: 'checkbox',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
'x-use-component-props': 'useTableBlockProps',
|
||||||
|
properties: {
|
||||||
|
actions: {
|
||||||
|
type: 'void',
|
||||||
|
title: '{{ t("Actions") }}',
|
||||||
|
version: '2.0',
|
||||||
|
'x-toolbar': 'TableColumnSchemaToolbar',
|
||||||
|
'x-settings': 'fieldSettings:TableColumn',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-decorator': 'TableV2.Column.ActionBar',
|
||||||
|
'x-app-version': '1.6.21',
|
||||||
|
'x-initializer': 'table:configureItemActions',
|
||||||
|
'x-action-column': 'actions',
|
||||||
|
'x-toolbar-props': {
|
||||||
|
initializer: 'table:configureItemActions',
|
||||||
|
},
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
properties: {
|
||||||
|
wwt3qrk0ro0: {
|
||||||
|
type: 'void',
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'Space',
|
||||||
|
'x-decorator': 'DndContext',
|
||||||
|
'x-app-version': '1.6.21',
|
||||||
|
'x-component-props': {
|
||||||
|
split: '|',
|
||||||
|
},
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
'x-uid': '58ymkonaijm',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '46zml84998i',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
d1qruxltt1t: {
|
||||||
|
type: 'void',
|
||||||
|
version: '2.0',
|
||||||
|
'x-toolbar': 'TableColumnSchemaToolbar',
|
||||||
|
'x-settings': 'fieldSettings:TableColumn',
|
||||||
|
'x-component': 'TableV2.Column',
|
||||||
|
'x-decorator': 'TableV2.Column.Decorator',
|
||||||
|
'x-app-version': '1.6.21',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
properties: {
|
||||||
|
nickname: {
|
||||||
|
version: '2.0',
|
||||||
|
'x-component': 'CollectionField',
|
||||||
|
'x-decorator': null,
|
||||||
|
'x-app-version': '1.6.21',
|
||||||
|
'x-read-pretty': true,
|
||||||
|
'x-component-props': {
|
||||||
|
ellipsis: true,
|
||||||
|
},
|
||||||
|
'x-decorator-props': {
|
||||||
|
labelStyle: {
|
||||||
|
display: 'none',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-collection-field': 'users.nickname',
|
||||||
|
_isJSONSchemaObject: true,
|
||||||
|
'x-uid': 'ut8m8l3qhzn',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'c2592dzb9s1',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'nntvcjy39cg',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'o5gizqch1wr',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': '8awil00a9wo',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
'x-uid': 'i1dnlv9n2k0',
|
||||||
|
'x-async': false,
|
||||||
|
'x-index': 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: 'ri6dthungp3',
|
||||||
|
'x-uid': 'b2u59skxpsy',
|
||||||
|
'x-async': true,
|
||||||
|
'x-index': 1,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { APIClient } from '../api-client';
|
import { APIClient } from '../api-client';
|
||||||
|
import { Application } from '../application';
|
||||||
|
|
||||||
class MockAPIClient extends APIClient {
|
class MockAPIClient extends APIClient {
|
||||||
mockAdapter() {
|
mockAdapter() {
|
||||||
@ -18,6 +19,9 @@ class MockAPIClient extends APIClient {
|
|||||||
|
|
||||||
export const mockAPIClient = () => {
|
export const mockAPIClient = () => {
|
||||||
const apiClient = new MockAPIClient();
|
const apiClient = new MockAPIClient();
|
||||||
|
const app = new Application();
|
||||||
|
apiClient.app = app;
|
||||||
|
|
||||||
const mockRequest = apiClient.mockAdapter();
|
const mockRequest = apiClient.mockAdapter();
|
||||||
return { apiClient, mockRequest };
|
return { apiClient, mockRequest };
|
||||||
};
|
};
|
||||||
|
@ -129,7 +129,7 @@ mockRequest.onGet('/users/0/belongsToField:get').reply(() => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
mockRequest.onGet('/users/0/hasManyField:list?pageSize=9999').reply(() => {
|
mockRequest.onGet('/users/0/hasManyField:list?paginate=false').reply(() => {
|
||||||
return [
|
return [
|
||||||
200,
|
200,
|
||||||
{
|
{
|
||||||
@ -142,7 +142,7 @@ mockRequest.onGet('/users/0/hasManyField:list?pageSize=9999').reply(() => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
mockRequest.onGet('/users/0/belongsToManyField:list?pageSize=9999').reply(() => {
|
mockRequest.onGet('/users/0/belongsToManyField:list?paginate=false').reply(() => {
|
||||||
return [
|
return [
|
||||||
200,
|
200,
|
||||||
{
|
{
|
||||||
@ -156,7 +156,7 @@ mockRequest.onGet('/users/0/belongsToManyField:list?pageSize=9999').reply(() =>
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
mockRequest.onGet('/test/0/hasManyField:list?pageSize=9999').reply(() => {
|
mockRequest.onGet('/test/0/hasManyField:list?paginate=false').reply(() => {
|
||||||
return [
|
return [
|
||||||
200,
|
200,
|
||||||
{
|
{
|
||||||
|
@ -8,11 +8,11 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const TYPE_TO_ACTION = {
|
const TYPE_TO_ACTION = {
|
||||||
hasMany: 'list?pageSize=9999',
|
hasMany: 'list?paginate=false',
|
||||||
belongsTo: 'get',
|
belongsTo: 'get',
|
||||||
hasOne: 'get',
|
hasOne: 'get',
|
||||||
belongsToMany: 'list?pageSize=9999',
|
belongsToMany: 'list?paginate=false',
|
||||||
belongsToArray: 'get',
|
belongsToArray: 'list?paginate=false',
|
||||||
};
|
};
|
||||||
export const getAction = (type: string) => {
|
export const getAction = (type: string) => {
|
||||||
if (process.env.NODE_ENV !== 'production' && !(type in TYPE_TO_ACTION)) {
|
if (process.env.NODE_ENV !== 'production' && !(type in TYPE_TO_ACTION)) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "create-nocobase-app",
|
"name": "create-nocobase-app",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"main": "src/index.js",
|
"main": "src/index.js",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -8,7 +8,8 @@
|
|||||||
"axios": "^1.7.0",
|
"axios": "^1.7.0",
|
||||||
"chalk": "^4.1.1",
|
"chalk": "^4.1.1",
|
||||||
"commander": "^9.2.0",
|
"commander": "^9.2.0",
|
||||||
"tar": "6.1.11"
|
"fs-extra": "^11.3.0",
|
||||||
|
"tar": "^7.4.3"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"create-nocobase-app": "./bin/index.js"
|
"create-nocobase-app": "./bin/index.js"
|
||||||
|
@ -19,8 +19,9 @@ const cli = new Command('create-nocobase');
|
|||||||
cli
|
cli
|
||||||
.arguments('<name>', 'directory of new NocoBase app')
|
.arguments('<name>', 'directory of new NocoBase app')
|
||||||
.option('--quickstart', 'quickstart app creation')
|
.option('--quickstart', 'quickstart app creation')
|
||||||
|
.option('--skip-dev-dependencies')
|
||||||
.option('-a, --all-db-dialect', 'install all database dialect dependencies')
|
.option('-a, --all-db-dialect', 'install all database dialect dependencies')
|
||||||
.option('-d, --db-dialect <dbDialect>', 'database dialect, current support sqlite/mysql/postgres', 'sqlite')
|
.option('-d, --db-dialect [dbDialect]', 'database dialect, current support postgres, mysql, mariadb, kingbase')
|
||||||
.option('-e, --env <env>', 'environment variables write into .env file', concat, [])
|
.option('-e, --env <env>', 'environment variables write into .env file', concat, [])
|
||||||
.description('create a new application')
|
.description('create a new application')
|
||||||
.action(async (name, options) => {
|
.action(async (name, options) => {
|
||||||
|
@ -9,7 +9,8 @@
|
|||||||
|
|
||||||
const chalk = require('chalk');
|
const chalk = require('chalk');
|
||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const { existsSync } = require('fs');
|
const { existsSync, promises } = require('fs');
|
||||||
|
const fs = require('fs-extra');
|
||||||
const { join, resolve } = require('path');
|
const { join, resolve } = require('path');
|
||||||
const { Generator } = require('@umijs/utils');
|
const { Generator } = require('@umijs/utils');
|
||||||
const { downloadPackageFromNpm, updateJsonFile } = require('./util');
|
const { downloadPackageFromNpm, updateJsonFile } = require('./util');
|
||||||
@ -36,21 +37,6 @@ class AppGenerator extends Generator {
|
|||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkDbEnv() {
|
|
||||||
const dialect = this.args.dbDialect;
|
|
||||||
const env = this.env;
|
|
||||||
if (dialect === 'sqlite') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!env.DB_DATABASE || !env.DB_USER || !env.DB_PASSWORD) {
|
|
||||||
console.log(
|
|
||||||
chalk.red(
|
|
||||||
`Please set DB_HOST, DB_PORT, DB_DATABASE, DB_USER, DB_PASSWORD in .env file to complete database settings`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
checkProjectPath() {
|
checkProjectPath() {
|
||||||
if (existsSync(this.cwd)) {
|
if (existsSync(this.cwd)) {
|
||||||
console.log(chalk.red('Project directory already exists'));
|
console.log(chalk.red('Project directory already exists'));
|
||||||
@ -58,44 +44,14 @@ class AppGenerator extends Generator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkDialect() {
|
|
||||||
const dialect = this.args.dbDialect;
|
|
||||||
const supportDialects = ['mysql', 'mariadb', 'sqlite', 'postgres'];
|
|
||||||
if (!supportDialects.includes(dialect)) {
|
|
||||||
console.log(
|
|
||||||
`dialect ${chalk.red(dialect)} is not supported, currently supported dialects are ${chalk.green(
|
|
||||||
supportDialects.join(','),
|
|
||||||
)}.`,
|
|
||||||
);
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getContext() {
|
getContext() {
|
||||||
const env = this.env;
|
const env = this.env;
|
||||||
const envs = [];
|
const envs = [];
|
||||||
const dependencies = [];
|
const dependencies = [];
|
||||||
const { dbDialect, allDbDialect } = this.args;
|
const { dbDialect } = this.args;
|
||||||
|
|
||||||
if (allDbDialect) {
|
|
||||||
dependencies.push(`"mysql2": "^3.11.0"`);
|
|
||||||
dependencies.push(`"mariadb": "^2.5.6"`);
|
|
||||||
dependencies.push(`"pg": "^8.7.3"`);
|
|
||||||
dependencies.push(`"pg-hstore": "^2.3.4"`);
|
|
||||||
dependencies.push(`"sqlite3": "^5.0.8"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (dbDialect) {
|
switch (dbDialect) {
|
||||||
case 'sqlite':
|
|
||||||
if (!allDbDialect) {
|
|
||||||
dependencies.push(`"sqlite3": "^5.0.8"`);
|
|
||||||
}
|
|
||||||
envs.push(`DB_STORAGE=${env.DB_STORAGE || 'storage/db/nocobase.sqlite'}`);
|
|
||||||
break;
|
|
||||||
case 'mysql':
|
case 'mysql':
|
||||||
if (!allDbDialect) {
|
|
||||||
dependencies.push(`"mysql2": "^3.11.0"`);
|
|
||||||
}
|
|
||||||
envs.push(`DB_HOST=${env.DB_HOST || 'localhost'}`);
|
envs.push(`DB_HOST=${env.DB_HOST || 'localhost'}`);
|
||||||
envs.push(`DB_PORT=${env.DB_PORT || 3306}`);
|
envs.push(`DB_PORT=${env.DB_PORT || 3306}`);
|
||||||
envs.push(`DB_DATABASE=${env.DB_DATABASE || ''}`);
|
envs.push(`DB_DATABASE=${env.DB_DATABASE || ''}`);
|
||||||
@ -103,9 +59,6 @@ class AppGenerator extends Generator {
|
|||||||
envs.push(`DB_PASSWORD=${env.DB_PASSWORD || ''}`);
|
envs.push(`DB_PASSWORD=${env.DB_PASSWORD || ''}`);
|
||||||
break;
|
break;
|
||||||
case 'mariadb':
|
case 'mariadb':
|
||||||
if (!allDbDialect) {
|
|
||||||
dependencies.push(`"mariadb": "^2.5.6"`);
|
|
||||||
}
|
|
||||||
envs.push(`DB_HOST=${env.DB_HOST || 'localhost'}`);
|
envs.push(`DB_HOST=${env.DB_HOST || 'localhost'}`);
|
||||||
envs.push(`DB_PORT=${env.DB_PORT || 3306}`);
|
envs.push(`DB_PORT=${env.DB_PORT || 3306}`);
|
||||||
envs.push(`DB_DATABASE=${env.DB_DATABASE || ''}`);
|
envs.push(`DB_DATABASE=${env.DB_DATABASE || ''}`);
|
||||||
@ -114,10 +67,6 @@ class AppGenerator extends Generator {
|
|||||||
break;
|
break;
|
||||||
case 'kingbase':
|
case 'kingbase':
|
||||||
case 'postgres':
|
case 'postgres':
|
||||||
if (!allDbDialect) {
|
|
||||||
dependencies.push(`"pg": "^8.7.3"`);
|
|
||||||
dependencies.push(`"pg-hstore": "^2.3.4"`);
|
|
||||||
}
|
|
||||||
envs.push(`DB_HOST=${env.DB_HOST || 'localhost'}`);
|
envs.push(`DB_HOST=${env.DB_HOST || 'localhost'}`);
|
||||||
envs.push(`DB_PORT=${env.DB_PORT || 5432}`);
|
envs.push(`DB_PORT=${env.DB_PORT || 5432}`);
|
||||||
envs.push(`DB_DATABASE=${env.DB_DATABASE || ''}`);
|
envs.push(`DB_DATABASE=${env.DB_DATABASE || ''}`);
|
||||||
@ -176,21 +125,33 @@ class AppGenerator extends Generator {
|
|||||||
|
|
||||||
async writing() {
|
async writing() {
|
||||||
this.checkProjectPath();
|
this.checkProjectPath();
|
||||||
this.checkDialect();
|
|
||||||
|
|
||||||
const { name } = this.context;
|
const { name } = this.context;
|
||||||
|
|
||||||
console.log(`Creating a new NocoBase application at ${chalk.green(name)}`);
|
console.log(`Creating a new NocoBase application at ${chalk.green(name)}`);
|
||||||
console.log('Creating files');
|
console.log('Creating files');
|
||||||
|
|
||||||
|
const context = this.getContext();
|
||||||
|
|
||||||
this.copyDirectory({
|
this.copyDirectory({
|
||||||
context: this.getContext(),
|
context,
|
||||||
path: join(__dirname, '../templates/app'),
|
path: join(__dirname, '../templates/app'),
|
||||||
target: this.cwd,
|
target: this.cwd,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.checkDbEnv();
|
const json = {
|
||||||
|
name: context.name,
|
||||||
|
...(await fs.readJSON(join(this.cwd, 'package.json'), 'utf8')),
|
||||||
|
};
|
||||||
|
|
||||||
|
json['dependencies']['@nocobase/cli'] = context.version;
|
||||||
|
|
||||||
|
if (!this.args.skipDevDependencies) {
|
||||||
|
json['devDependencies'] = json['devDependencies'] || {};
|
||||||
|
json['devDependencies']['@nocobase/devtools'] = context.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
await fs.writeJSON(join(this.cwd, 'package.json'), json, { encoding: 'utf8', spaces: 2 });
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log(chalk.green(`$ cd ${name}`));
|
console.log(chalk.green(`$ cd ${name}`));
|
||||||
console.log(chalk.green(`$ yarn install`));
|
console.log(chalk.green(`$ yarn install`));
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
{
|
{
|
||||||
"name": "{{{name}}}",
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*/*",
|
"packages/*/*",
|
||||||
@ -28,14 +27,16 @@
|
|||||||
"@types/react-dom": "^18.0.0",
|
"@types/react-dom": "^18.0.0",
|
||||||
"react-router-dom": "6.28.1",
|
"react-router-dom": "6.28.1",
|
||||||
"react-router": "6.28.1",
|
"react-router": "6.28.1",
|
||||||
|
"async": "^3.2.6",
|
||||||
"antd": "5.12.8",
|
"antd": "5.12.8",
|
||||||
"rollup": "4.24.0"
|
"rollup": "4.24.0",
|
||||||
|
"semver": "^7.7.1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/cli": "{{{version}}}",
|
"pm2": "^6.0.5",
|
||||||
{{{dependencies}}}
|
"mysql2": "^3.14.0",
|
||||||
},
|
"mariadb": "^2.5.6",
|
||||||
"devDependencies": {
|
"pg": "^8.14.1",
|
||||||
"@nocobase/devtools": "{{{version}}}"
|
"pg-hstore": "^2.3.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,18 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/data-source-manager",
|
"name": "@nocobase/data-source-manager",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/actions": "1.6.18",
|
"@nocobase/actions": "1.6.24",
|
||||||
"@nocobase/cache": "1.6.18",
|
"@nocobase/cache": "1.6.24",
|
||||||
"@nocobase/database": "1.6.18",
|
"@nocobase/database": "1.6.24",
|
||||||
"@nocobase/resourcer": "1.6.18",
|
"@nocobase/resourcer": "1.6.24",
|
||||||
"@nocobase/utils": "1.6.18",
|
"@nocobase/utils": "1.6.24",
|
||||||
"@types/jsonwebtoken": "^8.5.8",
|
"@types/jsonwebtoken": "^9.0.9",
|
||||||
"jsonwebtoken": "^8.5.1"
|
"jsonwebtoken": "^9.0.2"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/database",
|
"name": "@nocobase/database",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/logger": "1.6.18",
|
"@nocobase/logger": "1.6.24",
|
||||||
"@nocobase/utils": "1.6.18",
|
"@nocobase/utils": "1.6.24",
|
||||||
"async-mutex": "^0.3.2",
|
"async-mutex": "^0.3.2",
|
||||||
"chalk": "^4.1.1",
|
"chalk": "^4.1.1",
|
||||||
"cron-parser": "4.4.0",
|
"cron-parser": "4.4.0",
|
||||||
@ -20,12 +20,12 @@
|
|||||||
"graphlib": "^2.1.8",
|
"graphlib": "^2.1.8",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"mathjs": "^10.6.1",
|
"mathjs": "^10.6.1",
|
||||||
"nanoid": "^3.3.6",
|
"nanoid": "^3.3.11",
|
||||||
"node-fetch": "^2.6.7",
|
"node-fetch": "^2.6.7",
|
||||||
"node-sql-parser": "^4.18.0",
|
"node-sql-parser": "^4.18.0",
|
||||||
"qs": "^6.11.2",
|
"qs": "^6.11.2",
|
||||||
"safe-json-stringify": "^1.2.0",
|
"safe-json-stringify": "^1.2.0",
|
||||||
"semver": "^7.3.7",
|
"semver": "^7.7.1",
|
||||||
"sequelize": "^6.26.0",
|
"sequelize": "^6.26.0",
|
||||||
"umzug": "^3.1.1",
|
"umzug": "^3.1.1",
|
||||||
"uuid": "^9.0.1"
|
"uuid": "^9.0.1"
|
||||||
|
@ -15,3 +15,4 @@ export * from './datetime-interface';
|
|||||||
export * from './datetime-no-tz-interface';
|
export * from './datetime-no-tz-interface';
|
||||||
export * from './boolean-interface';
|
export * from './boolean-interface';
|
||||||
export * from './date-interface';
|
export * from './date-interface';
|
||||||
|
export * from './time-interface';
|
||||||
|
27
packages/core/database/src/interfaces/time-interface.ts
Normal file
27
packages/core/database/src/interfaces/time-interface.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
/**
|
||||||
|
* 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 dayjs from 'dayjs';
|
||||||
|
import utc from 'dayjs/plugin/utc';
|
||||||
|
import { BaseInterface } from './base-interface';
|
||||||
|
dayjs.extend(utc);
|
||||||
|
export class TimeInterface extends BaseInterface {
|
||||||
|
toValue(value: any, ctx?: any) {
|
||||||
|
if (this.validate(value)) {
|
||||||
|
const result = dayjs.utc(value).format('HH:mm:ss');
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
validate(value) {
|
||||||
|
const result = dayjs(value).isValid();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,7 @@ import {
|
|||||||
MultipleSelectInterface,
|
MultipleSelectInterface,
|
||||||
PercentInterface,
|
PercentInterface,
|
||||||
SelectInterface,
|
SelectInterface,
|
||||||
|
TimeInterface,
|
||||||
} from './index';
|
} from './index';
|
||||||
import { ManyToOneInterface } from './many-to-one-interface';
|
import { ManyToOneInterface } from './many-to-one-interface';
|
||||||
import { ManyToManyInterface } from './many-to-many-interface';
|
import { ManyToManyInterface } from './many-to-many-interface';
|
||||||
@ -50,6 +51,7 @@ const interfaces = {
|
|||||||
o2m: OneToManyInterface,
|
o2m: OneToManyInterface,
|
||||||
m2o: ManyToOneInterface,
|
m2o: ManyToOneInterface,
|
||||||
m2m: ManyToManyInterface,
|
m2m: ManyToManyInterface,
|
||||||
|
time: TimeInterface,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function registerInterfaces(db: Database) {
|
export function registerInterfaces(db: Database) {
|
||||||
|
@ -26,7 +26,7 @@ export class ViewCollection extends Collection {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return ['create', 'update', 'destroy'];
|
return ['create', 'update', 'destroy', 'importXlsx', 'destroyMany', 'updateMany'];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected sequelizeModelOptions(): any {
|
protected sequelizeModelOptions(): any {
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/devtools",
|
"name": "@nocobase/devtools",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"description": "",
|
"description": "",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./src/index.js",
|
"main": "./src/index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/build": "1.6.18",
|
"@nocobase/build": "1.6.24",
|
||||||
"@nocobase/client": "1.6.18",
|
"@nocobase/client": "1.6.24",
|
||||||
"@nocobase/test": "1.6.18",
|
"@nocobase/test": "1.6.24",
|
||||||
"@types/koa": "^2.15.0",
|
"@types/koa": "^2.15.0",
|
||||||
"@types/koa-bodyparser": "^4.3.4",
|
"@types/koa-bodyparser": "^4.3.4",
|
||||||
"@types/lodash": "^4.14.177",
|
"@types/lodash": "^4.14.177",
|
||||||
@ -35,7 +35,7 @@
|
|||||||
"react": "^18.0.0",
|
"react": "^18.0.0",
|
||||||
"react-dom": "^18.0.0",
|
"react-dom": "^18.0.0",
|
||||||
"rimraf": "^3.0.0",
|
"rimraf": "^3.0.0",
|
||||||
"serve": "^13.0.2",
|
"serve": "^14.2.4",
|
||||||
"ts-loader": "^7.0.4",
|
"ts-loader": "^7.0.4",
|
||||||
"ts-node": "9.1.1",
|
"ts-node": "9.1.1",
|
||||||
"ts-node-dev": "1.1.8",
|
"ts-node-dev": "1.1.8",
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/evaluators",
|
"name": "@nocobase/evaluators",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@formulajs/formulajs": "4.4.9",
|
"@formulajs/formulajs": "4.4.9",
|
||||||
"@nocobase/utils": "1.6.18",
|
"@nocobase/utils": "1.6.24",
|
||||||
"mathjs": "^10.6.0"
|
"mathjs": "^10.6.0"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/lock-manager",
|
"name": "@nocobase/lock-manager",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@nocobase/utils": "1.6.18",
|
"@nocobase/utils": "1.6.24",
|
||||||
"async-mutex": "^0.5.0"
|
"async-mutex": "^0.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/logger",
|
"name": "@nocobase/logger",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"description": "nocobase logging library",
|
"description": "nocobase logging library",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/resourcer",
|
"name": "@nocobase/resourcer",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/utils": "1.6.18",
|
"@nocobase/utils": "1.6.24",
|
||||||
"deepmerge": "^4.2.2",
|
"deepmerge": "^4.2.2",
|
||||||
"koa-compose": "^4.1.0",
|
"koa-compose": "^4.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"path-to-regexp": "6.2.2",
|
"path-to-regexp": "^6.3.0",
|
||||||
"qs": "^6.9.4"
|
"qs": "^6.9.4"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -367,7 +367,7 @@ export class ResourceManager {
|
|||||||
: params.resourceName;
|
: params.resourceName;
|
||||||
ctx.action.params.filterByTk = params.resourceIndex;
|
ctx.action.params.filterByTk = params.resourceIndex;
|
||||||
const query = parseQuery(ctx.request.querystring);
|
const query = parseQuery(ctx.request.querystring);
|
||||||
if (pathToRegexp('/resourcer/{:associatedName.}?:resourceName{\\::actionName}').test(ctx.request.path)) {
|
if (pathToRegexp('/resourcer/:rest(.*)').test(ctx.request.path)) {
|
||||||
ctx.action.mergeParams({
|
ctx.action.mergeParams({
|
||||||
...query,
|
...query,
|
||||||
...params,
|
...params,
|
||||||
|
@ -67,17 +67,25 @@ export function parseRequest(request: ParseRequest, options: ParseOptions = {}):
|
|||||||
...(options.accessors || {}),
|
...(options.accessors || {}),
|
||||||
};
|
};
|
||||||
const keys = [];
|
const keys = [];
|
||||||
const regexp = pathToRegexp('/resourcer/{:associatedName.}?:resourceName{\\::actionName}', keys);
|
|
||||||
|
const regexp = pathToRegexp('/resourcer/:rest(.*)', keys);
|
||||||
const reqPath = decodeURI(request.path);
|
const reqPath = decodeURI(request.path);
|
||||||
const matches = regexp.exec(reqPath);
|
const matches = regexp.exec(reqPath);
|
||||||
if (matches) {
|
if (matches) {
|
||||||
const params = {};
|
const params = {};
|
||||||
keys.forEach((obj, index) => {
|
const [resource, action] = matches[1].split(':');
|
||||||
if (matches[index + 1] === undefined) {
|
const [res1, res2] = resource.split('.');
|
||||||
return;
|
if (res1) {
|
||||||
|
if (res2) {
|
||||||
|
params['associatedName'] = res1;
|
||||||
|
params['resourceName'] = res2;
|
||||||
|
} else {
|
||||||
|
params['resourceName'] = res1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (action) {
|
||||||
|
params['actionName'] = action;
|
||||||
}
|
}
|
||||||
params[obj.name] = matches[index + 1];
|
|
||||||
});
|
|
||||||
return params;
|
return params;
|
||||||
}
|
}
|
||||||
const defaults = {
|
const defaults = {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/sdk",
|
"name": "@nocobase/sdk",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "lib/index.d.ts",
|
"types": "lib/index.d.ts",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/server",
|
"name": "@nocobase/server",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
@ -8,29 +8,30 @@
|
|||||||
"@formily/json-schema": "2.x",
|
"@formily/json-schema": "2.x",
|
||||||
"@hapi/topo": "^6.0.0",
|
"@hapi/topo": "^6.0.0",
|
||||||
"@koa/cors": "^5.0.0",
|
"@koa/cors": "^5.0.0",
|
||||||
"@koa/multer": "^3.0.2",
|
"@koa/multer": "^3.1.0",
|
||||||
"@koa/router": "^9.4.0",
|
"@koa/router": "^13.1.0",
|
||||||
"@nocobase/acl": "1.6.18",
|
"@nocobase/acl": "1.6.24",
|
||||||
"@nocobase/actions": "1.6.18",
|
"@nocobase/actions": "1.6.24",
|
||||||
"@nocobase/auth": "1.6.18",
|
"@nocobase/auth": "1.6.24",
|
||||||
"@nocobase/cache": "1.6.18",
|
"@nocobase/cache": "1.6.24",
|
||||||
"@nocobase/data-source-manager": "1.6.18",
|
"@nocobase/data-source-manager": "1.6.24",
|
||||||
"@nocobase/database": "1.6.18",
|
"@nocobase/database": "1.6.24",
|
||||||
"@nocobase/evaluators": "1.6.18",
|
"@nocobase/evaluators": "1.6.24",
|
||||||
"@nocobase/lock-manager": "1.6.18",
|
"@nocobase/lock-manager": "1.6.24",
|
||||||
"@nocobase/logger": "1.6.18",
|
"@nocobase/logger": "1.6.24",
|
||||||
"@nocobase/resourcer": "1.6.18",
|
"@nocobase/resourcer": "1.6.24",
|
||||||
"@nocobase/sdk": "1.6.18",
|
"@nocobase/sdk": "1.6.24",
|
||||||
"@nocobase/telemetry": "1.6.18",
|
"@nocobase/telemetry": "1.6.24",
|
||||||
"@nocobase/utils": "1.6.18",
|
"@nocobase/utils": "1.6.24",
|
||||||
"@types/decompress": "4.2.7",
|
"@types/decompress": "4.2.7",
|
||||||
"@types/ini": "^1.3.31",
|
"@types/ini": "^1.3.31",
|
||||||
"@types/koa-send": "^4.1.3",
|
"@types/koa-send": "^4.1.3",
|
||||||
"@types/multer": "^1.4.5",
|
"@types/multer": "^1.4.12",
|
||||||
"async-mutex": "^0.5.0",
|
"async-mutex": "^0.5.0",
|
||||||
"axios": "^1.7.0",
|
"axios": "^1.7.0",
|
||||||
"chalk": "^4.1.1",
|
"chalk": "^4.1.1",
|
||||||
"commander": "^9.2.0",
|
"commander": "^9.2.0",
|
||||||
|
"compression": "^1.8.0",
|
||||||
"cron": "^2.4.4",
|
"cron": "^2.4.4",
|
||||||
"cronstrue": "^2.11.0",
|
"cronstrue": "^2.11.0",
|
||||||
"dayjs": "^1.11.8",
|
"dayjs": "^1.11.8",
|
||||||
@ -44,10 +45,10 @@
|
|||||||
"koa-send": "^5.0.1",
|
"koa-send": "^5.0.1",
|
||||||
"koa-static": "^5.0.0",
|
"koa-static": "^5.0.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"multer": "^1.4.2",
|
"multer": "^1.4.5-lts.2",
|
||||||
"nanoid": "3.3.4",
|
"nanoid": "^3.3.11",
|
||||||
"semver": "^7.3.7",
|
"semver": "^7.7.1",
|
||||||
"serve-handler": "^6.1.5",
|
"serve-handler": "^6.1.6",
|
||||||
"ws": "^8.13.0",
|
"ws": "^8.13.0",
|
||||||
"xpipe": "^1.0.5"
|
"xpipe": "^1.0.5"
|
||||||
},
|
},
|
||||||
|
@ -29,14 +29,13 @@ export default (app: Application) => {
|
|||||||
'migrations',
|
'migrations',
|
||||||
`${dayjs().format('YYYYMMDDHHmmss')}-${name}.ts`,
|
`${dayjs().format('YYYYMMDDHHmmss')}-${name}.ts`,
|
||||||
);
|
);
|
||||||
const version = app.getVersion();
|
const version = app.getPackageVersion();
|
||||||
// 匹配主版本号、次版本号、小版本号和后缀的正则表达式
|
|
||||||
const regex = /(\d+)\.(\d+)\.(\d+)(-[\w.]+)?/;
|
const regex = /(\d+)\.(\d+)\.(\d+)(-[\w.]+)?/;
|
||||||
const nextVersion = version.replace(regex, (match, major, minor, patch, suffix) => {
|
const nextVersion = version.replace(regex, (match, major, minor, patch, suffix) => {
|
||||||
// 将小版本号转换为整数并加1
|
if (version.includes('beta') || version.includes('alpha')) {
|
||||||
const newPatch = parseInt(patch) + 1;
|
return `${major}.${minor}.${patch}`;
|
||||||
// 返回新的版本号
|
}
|
||||||
return `${major}.${minor}.${newPatch}${suffix || ''}`;
|
return `${major}.${1 + 1 * minor}.0`;
|
||||||
});
|
});
|
||||||
const from = pkg === '@nocobase/server' ? `../migration` : '@nocobase/server';
|
const from = pkg === '@nocobase/server' ? `../migration` : '@nocobase/server';
|
||||||
const data = `import { Migration } from '${from}';
|
const data = `import { Migration } from '${from}';
|
||||||
|
@ -15,7 +15,7 @@ const deps: Record<string, string> = {
|
|||||||
'@formily': '2.x',
|
'@formily': '2.x',
|
||||||
|
|
||||||
'@formily/antd-v5': '1.x',
|
'@formily/antd-v5': '1.x',
|
||||||
jsonwebtoken: '8.x',
|
jsonwebtoken: '9.x',
|
||||||
'cache-manager': '5.x',
|
'cache-manager': '5.x',
|
||||||
sequelize: '6.x',
|
sequelize: '6.x',
|
||||||
umzug: '3.x',
|
umzug: '3.x',
|
||||||
@ -26,7 +26,7 @@ const deps: Record<string, string> = {
|
|||||||
'winston-daily-rotate-file': '4.x',
|
'winston-daily-rotate-file': '4.x',
|
||||||
koa: '2.x',
|
koa: '2.x',
|
||||||
'@koa/cors': '5.x',
|
'@koa/cors': '5.x',
|
||||||
'@koa/router': '9.x',
|
'@koa/router': '13.x',
|
||||||
multer: '1.x',
|
multer: '1.x',
|
||||||
'@koa/multer': '3.x',
|
'@koa/multer': '3.x',
|
||||||
'koa-bodyparser': '4.x',
|
'koa-bodyparser': '4.x',
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/telemetry",
|
"name": "@nocobase/telemetry",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"description": "nocobase telemetry library",
|
"description": "nocobase telemetry library",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./lib/index.js",
|
"main": "./lib/index.js",
|
||||||
@ -11,7 +11,7 @@
|
|||||||
"directory": "packages/telemetry"
|
"directory": "packages/telemetry"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nocobase/utils": "1.6.18",
|
"@nocobase/utils": "1.6.24",
|
||||||
"@opentelemetry/api": "^1.7.0",
|
"@opentelemetry/api": "^1.7.0",
|
||||||
"@opentelemetry/instrumentation": "^0.46.0",
|
"@opentelemetry/instrumentation": "^0.46.0",
|
||||||
"@opentelemetry/resources": "^1.19.0",
|
"@opentelemetry/resources": "^1.19.0",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/test",
|
"name": "@nocobase/test",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"module": "./src/index.ts",
|
"module": "./src/index.ts",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
@ -51,7 +51,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@faker-js/faker": "8.1.0",
|
"@faker-js/faker": "8.1.0",
|
||||||
"@nocobase/server": "1.6.18",
|
"@nocobase/server": "1.6.24",
|
||||||
"@playwright/test": "^1.45.3",
|
"@playwright/test": "^1.45.3",
|
||||||
"@testing-library/jest-dom": "^6.4.2",
|
"@testing-library/jest-dom": "^6.4.2",
|
||||||
"@testing-library/react": "^14.0.0",
|
"@testing-library/react": "^14.0.0",
|
||||||
@ -69,7 +69,6 @@
|
|||||||
"mysql2": "^3.11.0",
|
"mysql2": "^3.11.0",
|
||||||
"pg": "^8.7.3",
|
"pg": "^8.7.3",
|
||||||
"pg-hstore": "^2.3.4",
|
"pg-hstore": "^2.3.4",
|
||||||
"sqlite3": "^5.0.8",
|
|
||||||
"supertest": "^6.1.6",
|
"supertest": "^6.1.6",
|
||||||
"vite": "^5.0.0",
|
"vite": "^5.0.0",
|
||||||
"vitest": "^1.5.0",
|
"vitest": "^1.5.0",
|
||||||
|
@ -31,6 +31,21 @@ function getPageMenuSchema({ pageSchemaUid, tabSchemaUid, tabSchemaName }) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getPageMenuSchemaWithTabSchema({ tabSchema }) {
|
||||||
|
if (!tabSchema) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'void',
|
||||||
|
'x-component': 'Page',
|
||||||
|
properties: {
|
||||||
|
[tabSchema.name]: tabSchema,
|
||||||
|
},
|
||||||
|
'x-uid': uid(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export * from '@playwright/test';
|
export * from '@playwright/test';
|
||||||
|
|
||||||
export { defineConfig };
|
export { defineConfig };
|
||||||
@ -193,10 +208,17 @@ export interface PageConfig {
|
|||||||
*/
|
*/
|
||||||
collections?: CollectionSetting[];
|
collections?: CollectionSetting[];
|
||||||
/**
|
/**
|
||||||
|
* @deprecate 在菜单被重构之后,没有办法直接复制完整的页面 Schema 了。所以这个选项不推荐使用了。
|
||||||
|
* 推荐使用 tabSchema,复制一个页面 tab 的 Schema 传给 tabSchema。
|
||||||
|
*
|
||||||
* 页面整体的 Schema
|
* 页面整体的 Schema
|
||||||
* @default undefined
|
* @default undefined
|
||||||
*/
|
*/
|
||||||
pageSchema?: any;
|
pageSchema?: any;
|
||||||
|
/**
|
||||||
|
* 页面 Tab 的 Schema。当 pageSchema 和 tabSchema 都存在时,最终显示的会是 tabSchema 的内容
|
||||||
|
*/
|
||||||
|
tabSchema?: any;
|
||||||
/** 如果为 true 则表示不会更改 PageSchema 的 uid */
|
/** 如果为 true 则表示不会更改 PageSchema 的 uid */
|
||||||
keepUid?: boolean;
|
keepUid?: boolean;
|
||||||
/** 在 URL 中的 uid,例如:/admin/0ig6xhe03u2 */
|
/** 在 URL 中的 uid,例如:/admin/0ig6xhe03u2 */
|
||||||
@ -217,6 +239,7 @@ interface CreatePageOptions {
|
|||||||
url?: PageConfig['url'];
|
url?: PageConfig['url'];
|
||||||
name?: string;
|
name?: string;
|
||||||
pageSchema?: any;
|
pageSchema?: any;
|
||||||
|
tabSchema?: any;
|
||||||
/** 如果为 true 则表示不会更改 PageSchema 的 uid */
|
/** 如果为 true 则表示不会更改 PageSchema 的 uid */
|
||||||
keepUid?: boolean;
|
keepUid?: boolean;
|
||||||
/** 在 URL 中的 uid,例如:/admin/0ig6xhe03u2 */
|
/** 在 URL 中的 uid,例如:/admin/0ig6xhe03u2 */
|
||||||
@ -367,6 +390,7 @@ export class NocoPage {
|
|||||||
type: this.options?.type,
|
type: this.options?.type,
|
||||||
name: this.options?.name,
|
name: this.options?.name,
|
||||||
pageSchema: this.options?.pageSchema,
|
pageSchema: this.options?.pageSchema,
|
||||||
|
tabSchema: this.options?.tabSchema,
|
||||||
url: this.options?.url,
|
url: this.options?.url,
|
||||||
keepUid: this.options?.keepUid,
|
keepUid: this.options?.keepUid,
|
||||||
pageUid: this.options?.pageUid,
|
pageUid: this.options?.pageUid,
|
||||||
@ -737,13 +761,16 @@ const updateUidOfPageSchema = (uiSchema: any) => {
|
|||||||
* 在 NocoBase 中创建一个页面
|
* 在 NocoBase 中创建一个页面
|
||||||
*/
|
*/
|
||||||
const createPage = async (options?: CreatePageOptions) => {
|
const createPage = async (options?: CreatePageOptions) => {
|
||||||
const { type = 'page', url, name, pageSchema, keepUid, pageUid: pageUidFromOptions } = options || {};
|
const { type = 'page', url, name, pageSchema, tabSchema, keepUid, pageUid: pageUidFromOptions } = options || {};
|
||||||
const api = await request.newContext({
|
const api = await request.newContext({
|
||||||
storageState: process.env.PLAYWRIGHT_AUTH_FILE,
|
storageState: process.env.PLAYWRIGHT_AUTH_FILE,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const schema = getPageMenuSchemaWithTabSchema({ tabSchema }) || pageSchema;
|
||||||
|
|
||||||
const state = await api.storageState();
|
const state = await api.storageState();
|
||||||
const headers = getHeaders(state);
|
const headers = getHeaders(state);
|
||||||
const newPageSchema = keepUid ? pageSchema : updateUidOfPageSchema(pageSchema);
|
const newPageSchema = keepUid ? schema : updateUidOfPageSchema(schema);
|
||||||
const pageSchemaUid = newPageSchema?.['x-uid'] || uid();
|
const pageSchemaUid = newPageSchema?.['x-uid'] || uid();
|
||||||
const newTabSchemaUid = uid();
|
const newTabSchemaUid = uid();
|
||||||
const newTabSchemaName = uid();
|
const newTabSchemaName = uid();
|
||||||
|
@ -7,13 +7,13 @@
|
|||||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import http from 'http';
|
|
||||||
import url from 'url';
|
|
||||||
import pg from 'pg';
|
|
||||||
import dotenv from 'dotenv';
|
import dotenv from 'dotenv';
|
||||||
import path from 'path';
|
import http from 'http';
|
||||||
import mysql from 'mysql2/promise';
|
|
||||||
import mariadb from 'mariadb';
|
import mariadb from 'mariadb';
|
||||||
|
import mysql from 'mysql2/promise';
|
||||||
|
import path from 'path';
|
||||||
|
import pg from 'pg';
|
||||||
|
import url from 'url';
|
||||||
|
|
||||||
dotenv.config({ path: path.resolve(process.cwd(), '.env.test') });
|
dotenv.config({ path: path.resolve(process.cwd(), '.env.test') });
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/utils",
|
"name": "@nocobase/utils",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"types": "./lib/index.d.ts",
|
"types": "./lib/index.d.ts",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
@ -13,7 +13,7 @@
|
|||||||
"flat-to-nested": "^1.1.1",
|
"flat-to-nested": "^1.1.1",
|
||||||
"graphlib": "^2.1.8",
|
"graphlib": "^2.1.8",
|
||||||
"handlebars": "^4.7.8",
|
"handlebars": "^4.7.8",
|
||||||
"multer": "^1.4.5-lts.1",
|
"multer": "^1.4.5-lts.2",
|
||||||
"object-path": "^0.11.8"
|
"object-path": "^0.11.8"
|
||||||
},
|
},
|
||||||
"gitHead": "d0b4efe4be55f8c79a98a331d99d9f8cf99021a1"
|
"gitHead": "d0b4efe4be55f8c79a98a331d99d9f8cf99021a1"
|
||||||
|
@ -54,3 +54,47 @@ export const hasEmptyValue = (objOrArr: object | any[]) => {
|
|||||||
export const nextTick = (fn: () => void) => {
|
export const nextTick = (fn: () => void) => {
|
||||||
setTimeout(fn);
|
setTimeout(fn);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通用树节点深度优先遍历函数
|
||||||
|
* @param {Object|Array} tree - 要遍历的树结构
|
||||||
|
* @param {Function} callback - 遍历每个节点时执行的回调函数,返回真值时停止遍历并返回当前节点
|
||||||
|
* @param {Object} options - 配置选项
|
||||||
|
* @param {string|Function} options.childrenKey - 子节点的属性名,默认为'children',也可以是一个函数
|
||||||
|
* @returns {any|undefined} - 找到的节点或undefined
|
||||||
|
*/
|
||||||
|
export function treeFind<T = any>(
|
||||||
|
tree: T | T[],
|
||||||
|
callback: (node: T) => boolean,
|
||||||
|
options: {
|
||||||
|
childrenKey?: string | ((node: T) => T[] | undefined);
|
||||||
|
} = {},
|
||||||
|
): T | undefined {
|
||||||
|
if (!tree) return undefined;
|
||||||
|
|
||||||
|
const { childrenKey = 'children' } = options;
|
||||||
|
|
||||||
|
// 处理根节点是数组的情况
|
||||||
|
const nodes = Array.isArray(tree) ? [...tree] : [tree];
|
||||||
|
|
||||||
|
// 深度优先搜索
|
||||||
|
for (const node of nodes) {
|
||||||
|
// 对当前节点调用回调函数
|
||||||
|
if (callback(node)) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取子节点
|
||||||
|
const children = typeof childrenKey === 'function' ? childrenKey(node) : (node as any)[childrenKey];
|
||||||
|
|
||||||
|
// 递归处理子节点
|
||||||
|
if (Array.isArray(children) && children.length > 0) {
|
||||||
|
const found = treeFind(children, callback, options);
|
||||||
|
if (found !== undefined) {
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
@ -69,12 +69,13 @@ export const toLocal = (value: dayjs.Dayjs) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const convertQuarterToFirstDay = (quarterStr) => {
|
const convertQuarterToFirstDay = (quarterStr) => {
|
||||||
if (dayjs(quarterStr).isValid()) {
|
try {
|
||||||
const year = parseInt(quarterStr.slice(0, 4)); // 提取年份
|
const year = parseInt(quarterStr.slice(0, 4)); // 提取年份
|
||||||
const quarter = parseInt(quarterStr.slice(-1)); // 提取季度数字
|
const quarter = parseInt(quarterStr.slice(-1)); // 提取季度数字
|
||||||
return dayjs().quarter(quarter).year(year);
|
return dayjs().quarter(quarter).year(year);
|
||||||
}
|
} catch (error) {
|
||||||
return null;
|
return null;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const toMoment = (val: any, options?: Str2momentOptions) => {
|
const toMoment = (val: any, options?: Str2momentOptions) => {
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"displayName.zh-CN": "权限控制",
|
"displayName.zh-CN": "权限控制",
|
||||||
"description": "Based on roles, resources, and actions, access control can precisely manage interface configuration permissions, data operation permissions, menu access permissions, and plugin permissions.",
|
"description": "Based on roles, resources, and actions, access control can precisely manage interface configuration permissions, data operation permissions, menu access permissions, and plugin permissions.",
|
||||||
"description.zh-CN": "基于角色、资源和操作的权限控制,可以精确控制界面配置权限、数据操作权限、菜单访问权限、插件权限。",
|
"description.zh-CN": "基于角色、资源和操作的权限控制,可以精确控制界面配置权限、数据操作权限、菜单访问权限、插件权限。",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"license": "AGPL-3.0",
|
"license": "AGPL-3.0",
|
||||||
"main": "./dist/server/index.js",
|
"main": "./dist/server/index.js",
|
||||||
"homepage": "https://docs.nocobase.com/handbook/acl",
|
"homepage": "https://docs.nocobase.com/handbook/acl",
|
||||||
@ -13,8 +13,8 @@
|
|||||||
"Users & permissions"
|
"Users & permissions"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jsonwebtoken": "^8.5.8",
|
"@types/jsonwebtoken": "^9.0.9",
|
||||||
"jsonwebtoken": "^8.5.1",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0"
|
"react-dom": "^18.2.0"
|
||||||
},
|
},
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/plugin-action-bulk-edit",
|
"name": "@nocobase/plugin-action-bulk-edit",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"main": "dist/server/index.js",
|
"main": "dist/server/index.js",
|
||||||
"homepage": "https://docs.nocobase.com/handbook/action-bulk-edit",
|
"homepage": "https://docs.nocobase.com/handbook/action-bulk-edit",
|
||||||
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-bulk-edit",
|
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-bulk-edit",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/plugin-action-bulk-update",
|
"name": "@nocobase/plugin-action-bulk-update",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"main": "dist/server/index.js",
|
"main": "dist/server/index.js",
|
||||||
"homepage": "https://docs.nocobase.com/handbook/action-bulk-update",
|
"homepage": "https://docs.nocobase.com/handbook/action-bulk-update",
|
||||||
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-bulk-update",
|
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-bulk-update",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nocobase/plugin-action-custom-request",
|
"name": "@nocobase/plugin-action-custom-request",
|
||||||
"version": "1.6.18",
|
"version": "1.6.24",
|
||||||
"main": "dist/server/index.js",
|
"main": "dist/server/index.js",
|
||||||
"homepage": "https://docs.nocobase.com/handbook/action-custom-request",
|
"homepage": "https://docs.nocobase.com/handbook/action-custom-request",
|
||||||
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-custom-request",
|
"homepage.zh-CN": "https://docs-cn.nocobase.com/handbook/action-custom-request",
|
||||||
|
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