├── .changeset ├── README.md └── config.json ├── .eslintrc.cjs ├── .github ├── commit-convention.md ├── renovate.json5 └── workflows │ ├── ci.yml │ ├── docs.yml │ └── release.yml ├── .gitignore ├── .husky ├── commit-msg ├── post-merge └── pre-commit ├── .npmrc ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── CHANGELOG.zh-CN.md ├── README.md ├── codecov.yml ├── commitlint.config.js ├── docs ├── .vitepress │ └── config.mts ├── api-examples.md ├── index.md ├── logo.png └── markdown-examples.md ├── enc.code-workspace ├── package.json ├── packages ├── base │ ├── package.json │ ├── src │ │ ├── biz-form │ │ │ ├── __tests__ │ │ │ │ └── biz.spec.ts │ │ │ ├── biz.ts │ │ │ ├── index.ts │ │ │ └── transformer.ts │ │ ├── env.d.ts │ │ ├── form-analyzer │ │ │ ├── BizFormAnalyzer.ts │ │ │ ├── FormAnalyzer.ts │ │ │ ├── __tests__ │ │ │ │ └── FormAnalyzer.spec.ts │ │ │ └── index.ts │ │ ├── form │ │ │ ├── __tests__ │ │ │ │ ├── cascader.spec.ts │ │ │ │ └── upload.spec.ts │ │ │ ├── base.ts │ │ │ ├── cascader.ts │ │ │ ├── checkbox.ts │ │ │ ├── custom.ts │ │ │ ├── date.ts │ │ │ ├── index.ts │ │ │ ├── input.ts │ │ │ ├── popup.ts │ │ │ ├── radio.ts │ │ │ ├── rate.ts │ │ │ ├── select.ts │ │ │ ├── switch.ts │ │ │ ├── time.ts │ │ │ └── upload.ts │ │ ├── index.ts │ │ └── utils │ │ │ ├── ImageUtils.ts │ │ │ └── index.ts │ ├── tsconfig.build.json │ └── vite.config.ts ├── extension-form-editor │ ├── package.json │ ├── src │ │ ├── biz │ │ │ ├── __tests__ │ │ │ │ └── feature.spec.ts │ │ │ ├── feature.ts │ │ │ ├── index.ts │ │ │ └── transformer.ts │ │ ├── config │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── preset │ │ │ ├── __tests__ │ │ │ │ ├── feature-group.spec.ts │ │ │ │ └── feature.spec.ts │ │ │ ├── feature-group.ts │ │ │ ├── feature.ts │ │ │ └── index.ts │ │ └── utils │ │ │ ├── __tests__ │ │ │ └── index.spec.ts │ │ │ └── index.ts │ ├── tsconfig.build.json │ └── vite.config.ts ├── extension-vue-form-editor │ ├── .eslintrc.cjs │ ├── package.json │ ├── postcss.config.cjs │ ├── src │ │ ├── __tests__ │ │ │ └── mock-enc-form.ts │ │ ├── components │ │ │ ├── fieldset │ │ │ │ ├── Fieldset.vue │ │ │ │ ├── __tests__ │ │ │ │ │ └── Fieldset.spec.tsx │ │ │ │ └── index.ts │ │ │ ├── form-edit-panel │ │ │ │ ├── FormEditPanel.vue │ │ │ │ ├── center-panel │ │ │ │ │ ├── CenterPanel.vue │ │ │ │ │ ├── DrawableFormItem.vue │ │ │ │ │ ├── EmptyContainer.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── dnd.ts │ │ │ │ ├── index.ts │ │ │ │ ├── left-panel │ │ │ │ │ ├── DrawableFeature.vue │ │ │ │ │ ├── LeftPanel.vue │ │ │ │ │ └── index.ts │ │ │ │ └── right-panel │ │ │ │ │ ├── RightPanel.vue │ │ │ │ │ └── index.ts │ │ │ ├── form-editor-tip │ │ │ │ ├── FormEditorTip.vue │ │ │ │ ├── __tests__ │ │ │ │ │ └── FormEditorTip.spec.tsx │ │ │ │ └── index.ts │ │ │ ├── form-editor │ │ │ │ ├── FormEditor.vue │ │ │ │ └── index.ts │ │ │ ├── form-item-editor │ │ │ │ ├── base │ │ │ │ │ ├── BaseFormItemEditor.vue │ │ │ │ │ ├── __tests__ │ │ │ │ │ │ └── BaseFormItemEditor.spec.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ └── items.ts │ │ │ │ ├── checkbox │ │ │ │ │ ├── CheckboxFormItemEditor.vue │ │ │ │ │ ├── CheckboxFormItemOptionEditor.vue │ │ │ │ │ ├── index.ts │ │ │ │ │ └── items.ts │ │ │ │ ├── common.ts │ │ │ │ ├── date │ │ │ │ │ ├── DateFormItemEditor.vue │ │ │ │ │ ├── index.ts │ │ │ │ │ └── items.ts │ │ │ │ ├── index.ts │ │ │ │ ├── input │ │ │ │ │ ├── InputFormItemEditor.vue │ │ │ │ │ ├── index.ts │ │ │ │ │ └── items.ts │ │ │ │ ├── radio │ │ │ │ │ ├── RadioFormItemEditor.vue │ │ │ │ │ ├── RadioFormItemOptionEditor.vue │ │ │ │ │ ├── index.ts │ │ │ │ │ └── items.ts │ │ │ │ ├── rate │ │ │ │ │ ├── RateFormItemEditor.vue │ │ │ │ │ ├── index.ts │ │ │ │ │ └── items.ts │ │ │ │ ├── rules │ │ │ │ │ ├── RulesFormItemEditor.vue │ │ │ │ │ ├── index.ts │ │ │ │ │ └── items.ts │ │ │ │ ├── select │ │ │ │ │ ├── SelectFormItemEditor.vue │ │ │ │ │ ├── SelectFormItemOptionEditor.vue │ │ │ │ │ ├── index.ts │ │ │ │ │ └── items.ts │ │ │ │ ├── switch │ │ │ │ │ ├── SwitchFormItemEditor.vue │ │ │ │ │ ├── index.ts │ │ │ │ │ └── items.ts │ │ │ │ ├── time │ │ │ │ │ ├── TimeFormItemEditor.vue │ │ │ │ │ ├── index.ts │ │ │ │ │ └── items.ts │ │ │ │ └── upload │ │ │ │ │ ├── UploadFormItemEditor.vue │ │ │ │ │ ├── index.ts │ │ │ │ │ └── items.ts │ │ │ ├── form-preview │ │ │ │ ├── FormPreview.vue │ │ │ │ ├── __tests__ │ │ │ │ │ └── FormPreview.spec.tsx │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── hooks │ │ │ ├── __tests__ │ │ │ │ ├── use-editor-items.spec.tsx │ │ │ │ └── use-sync-options-editor-items.spec.tsx │ │ │ ├── index.ts │ │ │ ├── use-editor-items.ts │ │ │ └── use-sync-editor-options-items.ts │ │ ├── icons │ │ │ ├── Icon.vue │ │ │ ├── iconfont.js │ │ │ └── index.ts │ │ ├── index.ts │ │ └── styles │ │ │ ├── base.css │ │ │ └── index.ts │ ├── tailwind.config.cjs │ ├── tsconfig.build.json │ └── vite.config.ts ├── react │ ├── package.json │ ├── src │ │ └── index.ts │ └── tsconfig.json ├── shared │ ├── package.json │ ├── src │ │ ├── __tests__ │ │ │ ├── index.spec.ts │ │ │ └── type-utils.spec.ts │ │ ├── index.ts │ │ ├── type-utils.ts │ │ └── types.ts │ ├── tsconfig.build.json │ └── vite.config.ts ├── style │ ├── package.json │ ├── src │ │ ├── index.ts │ │ └── style.css │ ├── tsconfig.build.json │ └── vite.config.ts ├── test-utils │ ├── package.json │ └── src │ │ └── index.ts ├── vue-element-plus │ ├── .eslintrc.cjs │ ├── package.json │ ├── postcss.config.cjs │ ├── src │ │ ├── components │ │ │ ├── form │ │ │ │ ├── cascader │ │ │ │ │ ├── CascaderFormItem.vue │ │ │ │ │ ├── __tests__ │ │ │ │ │ │ └── CascaderFormItem.spec.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── checkbox │ │ │ │ │ ├── CheckboxFormItem.vue │ │ │ │ │ ├── __tests__ │ │ │ │ │ │ └── CheckboxFormItem.spec.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── date │ │ │ │ │ ├── DateFormItem.vue │ │ │ │ │ ├── __tests__ │ │ │ │ │ │ └── DateFormItem.spec.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── form-item │ │ │ │ │ ├── FormItem.vue │ │ │ │ │ ├── __tests__ │ │ │ │ │ │ └── FormItem.spec.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── form │ │ │ │ │ ├── Form.vue │ │ │ │ │ ├── __tests__ │ │ │ │ │ │ └── Form.spec.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── input │ │ │ │ │ ├── InputFormItem.vue │ │ │ │ │ ├── __tests__ │ │ │ │ │ │ └── InputFormItem.spec.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── radio │ │ │ │ │ ├── RadioFormItem.vue │ │ │ │ │ ├── __tests__ │ │ │ │ │ │ └── RadioFormItem.spec.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── rate │ │ │ │ │ ├── RateFormItem.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── select │ │ │ │ │ ├── SelectFormItem.vue │ │ │ │ │ ├── __tests__ │ │ │ │ │ │ └── SelectFormItem.spec.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── switch │ │ │ │ │ ├── SwitchFormItem.vue │ │ │ │ │ ├── __tests__ │ │ │ │ │ │ └── SwitchFormItem.spec.tsx │ │ │ │ │ └── index.ts │ │ │ │ ├── time │ │ │ │ │ ├── TimeFormItem.vue │ │ │ │ │ └── index.ts │ │ │ │ └── upload │ │ │ │ │ ├── UploadFormItem.vue │ │ │ │ │ └── index.ts │ │ │ └── index.ts │ │ ├── hooks │ │ │ ├── __tests__ │ │ │ │ ├── use-confirm.spec.ts │ │ │ │ ├── use-form.spec.tsx │ │ │ │ └── use-loading.spec.ts │ │ │ ├── index.ts │ │ │ ├── use-confirm.ts │ │ │ ├── use-form.ts │ │ │ └── use-loading.ts │ │ ├── index.ts │ │ ├── style.css │ │ └── upstream.ts │ ├── tailwind.config.cjs │ ├── tsconfig.build.json │ └── vite.config.ts ├── vue-vant │ ├── .eslintrc.cjs │ ├── package.json │ ├── postcss.config.cjs │ ├── src │ │ ├── components │ │ │ ├── form │ │ │ │ ├── cascader │ │ │ │ │ ├── CascaderFormItem.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── checkbox │ │ │ │ │ ├── CheckboxFormItem.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── date │ │ │ │ │ ├── DateFormItem.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── form-item │ │ │ │ │ ├── FormItem.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── form │ │ │ │ │ ├── Form.vue │ │ │ │ │ ├── index.ts │ │ │ │ │ └── provide.ts │ │ │ │ ├── index.ts │ │ │ │ ├── input │ │ │ │ │ ├── InputFormItem.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── popup │ │ │ │ │ ├── PopupFormItem.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── radio │ │ │ │ │ ├── RadioFormItem.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── rate │ │ │ │ │ ├── RateFormItem.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── select │ │ │ │ │ ├── SelectFormItem.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── switch │ │ │ │ │ ├── SwitchFormItem.vue │ │ │ │ │ └── index.ts │ │ │ │ ├── time │ │ │ │ │ ├── TimeFormItem.vue │ │ │ │ │ └── index.ts │ │ │ │ └── upload │ │ │ │ │ ├── UploadFormItem.vue │ │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── layout │ │ │ │ ├── Layout.vue │ │ │ │ └── index.ts │ │ │ └── router-transition │ │ │ │ ├── RouterTransition.vue │ │ │ │ └── index.ts │ │ ├── hooks │ │ │ ├── __tests__ │ │ │ │ ├── use-confirm.spec.ts │ │ │ │ ├── use-form.spec.tsx │ │ │ │ └── use-loading.spec.ts │ │ │ ├── index.ts │ │ │ ├── use-confirm.ts │ │ │ ├── use-form.ts │ │ │ └── use-loading.ts │ │ ├── index.ts │ │ ├── style.css │ │ └── upstream.ts │ ├── tailwind.config.cjs │ ├── tsconfig.build.json │ └── vite.config.ts └── vue │ ├── .eslintrc.cjs │ ├── package.json │ ├── postcss.config.cjs │ ├── src │ ├── __tests__ │ │ └── install.spec.tsx │ ├── components │ │ ├── index.ts │ │ ├── keep-alive-router-view │ │ │ ├── KeepAliveRouterView.vue │ │ │ └── index.ts │ │ └── transition │ │ │ ├── Fade.vue │ │ │ ├── Slide.vue │ │ │ ├── Transition.vue │ │ │ ├── Zoom.vue │ │ │ ├── hooks.ts │ │ │ └── index.ts │ ├── hooks │ │ ├── __tests__ │ │ │ ├── use-confirm.spec.ts │ │ │ ├── use-count-event.spec.ts │ │ │ ├── use-emitter.spec.ts │ │ │ ├── use-event-lock.spec.ts │ │ │ ├── use-form-data.spec.ts │ │ │ ├── use-form-items.spec.ts │ │ │ ├── use-form.spec.ts │ │ │ ├── use-loading.spec.ts │ │ │ └── use-pagination.spec.ts │ │ ├── index.ts │ │ ├── use-confirm.ts │ │ ├── use-count-event.ts │ │ ├── use-emitter.ts │ │ ├── use-event-lock.ts │ │ ├── use-form-data.ts │ │ ├── use-form-items.ts │ │ ├── use-form.ts │ │ ├── use-loading.ts │ │ └── use-pagination.ts │ ├── index.ts │ ├── install.ts │ └── style.css │ ├── tsconfig.build.json │ └── vite.config.ts ├── playgrounds ├── .gitkeep └── vue │ ├── .eslintrc.cjs │ ├── index.html │ ├── package.json │ ├── postcss.config.cjs │ ├── src │ ├── App.vue │ ├── constants.ts │ ├── env.d.ts │ ├── main.ts │ ├── router │ │ ├── index.ts │ │ └── routes.ts │ ├── style.css │ └── views │ │ ├── form-editor │ │ ├── index.vue │ │ └── routes.ts │ │ └── form │ │ ├── Controller.vue │ │ ├── PreviewElementPlus.vue │ │ ├── PreviewVant.vue │ │ ├── index.vue │ │ └── routes.ts │ ├── tailwind.config.cjs │ └── vite.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts ├── changeset.ts ├── postcss.base.config.cjs ├── tailwind.base.config.cjs ├── unit-test.ts └── vite.base.config.ts ├── tsconfig.base.json ├── tsconfig.build.json ├── tsconfig.json ├── turbo.json └── vitest.config.ts /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.2.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@ombro/eslint-config-typescript'], 4 | rules: { 5 | 'prefer-const': 'off', 6 | '@typescript-eslint/no-empty-interface': 'off', 7 | '@typescript-eslint/no-non-null-assertion': 'off', 8 | '@typescript-eslint/no-explicit-any': 'off', 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | $schema: 'https://docs.renovatebot.com/renovate-schema.json', 3 | extends: ['config:base', 'schedule:weekly', 'group:allNonMajor'], 4 | assignees: ['Cphayim'], // PR 中的 assignees 包含这些人 5 | labels: ['dependencies'], // PR 中的 labels 包含这些标签 6 | rangeStrategy: 'bump', // 当前版本号的范围策略, bump: "v1.0.0 -> v1.1.0" 7 | // commitBodyTable: true, // 在 PR 正文中附加一个描述更新内容的表格 8 | ignorePaths: ['**/__tests__/**'], 9 | // ignoreDeps: ['typescript'], 10 | timezone: 'Asia/Shanghai', 11 | packageRules: [ 12 | { 13 | enabled: false, 14 | matchDepTypes: ['peerDependencies'], 15 | }, 16 | ], 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Deploy docs to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | # Allows you to run this workflow manually from the Actions tab 8 | workflow_dispatch: 9 | 10 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 11 | permissions: 12 | contents: read 13 | pages: write 14 | id-token: write 15 | 16 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 17 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 18 | concurrency: 19 | group: pages 20 | cancel-in-progress: false 21 | 22 | jobs: 23 | # Build job 24 | build: 25 | runs-on: ubuntu-latest 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@v4 29 | with: 30 | fetch-depth: 0 # Not needed if lastUpdated is not enabled 31 | - uses: pnpm/action-setup@v2 # Uncomment this if you're using pnpm 32 | # - uses: oven-sh/setup-bun@v1 # Uncomment this if you're using Bun 33 | - name: Setup Node 34 | uses: actions/setup-node@v4 35 | with: 36 | node-version: 20 37 | cache: pnpm 38 | - name: Setup Pages 39 | uses: actions/configure-pages@v4 40 | - name: Install dependencies 41 | run: pnpm install 42 | - name: Build with VitePress 43 | run: | 44 | pnpm docs:build 45 | touch docs/.vitepress/dist/.nojekyll 46 | - name: Upload artifact 47 | uses: actions/upload-pages-artifact@v3 48 | with: 49 | path: docs/.vitepress/dist 50 | 51 | # Deployment job 52 | deploy: 53 | environment: 54 | name: github-pages 55 | url: ${{ steps.deployment.outputs.page_url }} 56 | needs: build 57 | runs-on: ubuntu-latest 58 | name: Deploy 59 | steps: 60 | - name: Deploy to GitHub Pages 61 | id: deployment 62 | uses: actions/deploy-pages@v4 63 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | if: "startsWith(github.event.head_commit.message, 'chore(release):')" 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup pnpm 18 | uses: pnpm/action-setup@v2 19 | 20 | - name: Setup Node.js 21 | uses: actions/setup-node@v4 22 | with: 23 | node-version: 20 24 | cache: 'pnpm' 25 | 26 | - name: Get pnpm store directory 27 | id: pnpm-cache 28 | run: | 29 | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT 30 | - uses: actions/cache@v4 31 | name: Setup pnpm cache 32 | with: 33 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} 34 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 35 | restore-keys: | 36 | ${{ runner.os }}-pnpm-store- 37 | - name: Install dependencies 38 | run: pnpm install --frozen-lockfile 39 | 40 | - name: Run compile and build packages 41 | run: pnpm build 42 | 43 | - name: Publish Packages to NPM 44 | run: | 45 | echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> ~/.npmrc 46 | pnpm -r publish 47 | env: 48 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | 6 | # testing 7 | coverage 8 | 9 | # misc 10 | .DS_Store 11 | *.pem 12 | 13 | # debug 14 | npm-debug.log* 15 | yarn-debug.log* 16 | yarn-error.log* 17 | .pnpm-debug.log* 18 | 19 | # local env files 20 | .env.local 21 | .env.development.local 22 | .env.test.local 23 | .env.production.local 24 | 25 | # turbo 26 | .turbo 27 | 28 | # build 29 | dist 30 | tmp 31 | 32 | *.tsbuildinfo 33 | *timestamp-* 34 | 35 | # docs 36 | docs/.vitepress/dist 37 | docs/.vitepress/cache 38 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | echo "🛠🛠🛠 run commitlint" 5 | pnpm commitlint --edit 6 | -------------------------------------------------------------------------------- /.husky/post-merge: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | pnpm install 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | echo "🛠🛠🛠 run lint-staged" 5 | pnpm lint-staged 6 | 7 | echo "🛠🛠🛠 run test" 8 | pnpm test:unit --changed main 9 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | publish-branch = main 2 | link-workspace-packages = true 3 | strict-peer-dependencies=false 4 | 5 | public-hoist-pattern[]=vue 6 | public-hoist-pattern[]=@vue* 7 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | third-party.ts 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "printWidth": 100 6 | } 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | ombro - a customized toolbox for js 4 | 5 | ![GitHub Actions Status](https://github.com/Cphayim/enc/actions/workflows/ci.yml/badge.svg) 6 | [![Codecov](https://codecov.io/gh/Cphayim/enc/branch/main/graph/badge.svg?token=HQZZT3GKZF)](https://codecov.io/gh/Cphayim/enc) 7 | [![npm package](https://badgen.net/npm/v/@cphayim-enc/base)](https://www.npmjs.com/package/@cphayim-enc/base) 8 | [![pnpm](https://img.shields.io/badge/maintained%20with-pnpm-f49033.svg)](https://pnpm.io/) 9 | 10 |
11 | 12 | 13 | ## Changelogs 14 | 15 | [CHANGELOG.md](./CHANGELOG.md) 16 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | patch: 4 | default: 5 | target: 80% 6 | project: 7 | default: 8 | target: 80% 9 | threshold: 10% 10 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | module.exports = { extends: ['@commitlint/config-conventional'] } 3 | -------------------------------------------------------------------------------- /docs/.vitepress/config.mts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitepress' 2 | 3 | // https://vitepress.dev/reference/site-config 4 | export default defineConfig({ 5 | base: '/enc', 6 | title: "Enc", 7 | description: "A cross-framework consistency enhancer", 8 | themeConfig: { 9 | // https://vitepress.dev/reference/default-theme-config 10 | nav: [ 11 | { text: '首页', link: '/' }, 12 | { text: 'APIs', link: '/markdown-examples' } 13 | ], 14 | 15 | sidebar: [ 16 | { 17 | text: 'Examples', 18 | items: [ 19 | { text: 'Markdown Examples', link: '/markdown-examples' }, 20 | { text: 'Runtime API Examples', link: '/api-examples' } 21 | ] 22 | } 23 | ], 24 | 25 | socialLinks: [ 26 | { icon: 'github', link: 'https://github.com/Cphayim/enc' } 27 | ] 28 | } 29 | }) 30 | -------------------------------------------------------------------------------- /docs/api-examples.md: -------------------------------------------------------------------------------- 1 | --- 2 | outline: deep 3 | --- 4 | 5 | # Runtime API Examples 6 | 7 | This page demonstrates usage of some of the runtime APIs provided by VitePress. 8 | 9 | The main `useData()` API can be used to access site, theme, and page data for the current page. It works in both `.md` and `.vue` files: 10 | 11 | ```md 12 | 17 | 18 | ## Results 19 | 20 | ### Theme Data 21 |
{{ theme }}
22 | 23 | ### Page Data 24 |
{{ page }}
25 | 26 | ### Page Frontmatter 27 |
{{ frontmatter }}
28 | ``` 29 | 30 | 35 | 36 | ## Results 37 | 38 | ### Theme Data 39 |
{{ theme }}
40 | 41 | ### Page Data 42 |
{{ page }}
43 | 44 | ### Page Frontmatter 45 |
{{ frontmatter }}
46 | 47 | ## More 48 | 49 | Check out the documentation for the [full list of runtime APIs](https://vitepress.dev/reference/runtime-api#usedata). 50 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | # https://vitepress.dev/reference/default-theme-home-page 3 | layout: home 4 | 5 | hero: 6 | name: "Enc" 7 | text: "跨框架一致性的组合函数及组件工具库" 8 | tagline: 9 | actions: 10 | - theme: brand 11 | text: Markdown Examples 12 | link: /markdown-examples 13 | - theme: alt 14 | text: API Examples 15 | link: /api-examples 16 | 17 | features: 18 | - title: Feature A 19 | details: Lorem ipsum dolor sit amet, consectetur adipiscing elit 20 | - title: Feature B 21 | details: Lorem ipsum dolor sit amet, consectetur adipiscing elit 22 | - title: Feature C 23 | details: Lorem ipsum dolor sit amet, consectetur adipiscing elit 24 | --- 25 | -------------------------------------------------------------------------------- /docs/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cphayim/enc/9170ebe79fd8caf7512b0e54b507862864fedf13/docs/logo.png -------------------------------------------------------------------------------- /docs/markdown-examples.md: -------------------------------------------------------------------------------- 1 | # Markdown Extension Examples 2 | 3 | This page demonstrates some of the built-in markdown extensions provided by VitePress. 4 | 5 | ## Syntax Highlighting 6 | 7 | VitePress provides Syntax Highlighting powered by [Shiki](https://github.com/shikijs/shiki), with additional features like line-highlighting: 8 | 9 | **Input** 10 | 11 | ```` 12 | ```js{4} 13 | export default { 14 | data () { 15 | return { 16 | msg: 'Highlighted!' 17 | } 18 | } 19 | } 20 | ``` 21 | ```` 22 | 23 | **Output** 24 | 25 | ```js{4} 26 | export default { 27 | data () { 28 | return { 29 | msg: 'Highlighted!' 30 | } 31 | } 32 | } 33 | ``` 34 | 35 | ## Custom Containers 36 | 37 | **Input** 38 | 39 | ```md 40 | ::: info 41 | This is an info box. 42 | ::: 43 | 44 | ::: tip 45 | This is a tip. 46 | ::: 47 | 48 | ::: warning 49 | This is a warning. 50 | ::: 51 | 52 | ::: danger 53 | This is a dangerous warning. 54 | ::: 55 | 56 | ::: details 57 | This is a details block. 58 | ::: 59 | ``` 60 | 61 | **Output** 62 | 63 | ::: info 64 | This is an info box. 65 | ::: 66 | 67 | ::: tip 68 | This is a tip. 69 | ::: 70 | 71 | ::: warning 72 | This is a warning. 73 | ::: 74 | 75 | ::: danger 76 | This is a dangerous warning. 77 | ::: 78 | 79 | ::: details 80 | This is a details block. 81 | ::: 82 | 83 | ## More 84 | 85 | Check out the documentation for the [full list of markdown extensions](https://vitepress.dev/guide/markdown). 86 | -------------------------------------------------------------------------------- /enc.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "name": "✨ enc-monorepo", 5 | "path": "." 6 | }, 7 | { 8 | "name": "📦 @enc/shared", 9 | "path": "packages/shared" 10 | }, 11 | { 12 | "name": "📦 @enc/base", 13 | "path": "packages/base" 14 | }, 15 | { 16 | "name": "📦 @enc/style", 17 | "path": "packages/style" 18 | }, 19 | { 20 | "name": "📦 @enc/vue", 21 | "path": "packages/vue" 22 | }, 23 | { 24 | "name": "📦 @enc/vue-element-plus", 25 | "path": "packages/vue-element-plus" 26 | }, 27 | { 28 | "name": "📦 @enc/vue-vant", 29 | "path": "packages/vue-vant" 30 | }, 31 | { 32 | "name": "📦 @enc/extension-form-editor", 33 | "path": "packages/extension-form-editor" 34 | }, 35 | { 36 | "name": "📦 @enc/extension-vue-form-editor", 37 | "path": "packages/extension-vue-form-editor" 38 | }, 39 | { 40 | "name": "📺 @playground/vue", 41 | "path": "playgrounds/vue" 42 | } 43 | ], 44 | "settings": { 45 | "typescript.tsdk": "node_modules/typescript/lib", 46 | "cSpell.words": ["daterange"] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/base/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cphayim-enc/base", 3 | "version": "0.3.3", 4 | "description": "", 5 | "keywords": [], 6 | "author": "Cphayim ", 7 | "homepage": "https://github.com/Cphayim/enc#readme", 8 | "license": "ISC", 9 | "type": "module", 10 | "main": "./dist/index.js", 11 | "types": "./dist/index.d.ts", 12 | "exports": { 13 | ".": { 14 | "import": "./dist/index.js", 15 | "require": "./dist/index.cjs", 16 | "types": "./dist/index.d.ts" 17 | } 18 | }, 19 | "files": [ 20 | "dist" 21 | ], 22 | "scripts": { 23 | "dev": "vite build --mode=development --watch", 24 | "build": "vite build && tsc --declaration --emitDeclarationOnly -p tsconfig.build.json", 25 | "lint": "eslint src --ext .ts --fix", 26 | "test": "echo \"Error: no test specified\" && exit 1" 27 | }, 28 | "dependencies": { 29 | "@cphayim-enc/shared": "workspace:*", 30 | "image-conversion": "^2.1.1" 31 | }, 32 | "peerDependenciesMeta": {}, 33 | "publishConfig": { 34 | "access": "public", 35 | "registry": "https://registry.npmjs.org/" 36 | }, 37 | "repository": { 38 | "type": "git", 39 | "url": "git+https://github.com/Cphayim/enc.git", 40 | "directory": "packages/base" 41 | }, 42 | "bugs": { 43 | "url": "https://github.com/Cphayim/enc/issues" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/base/src/biz-form/index.ts: -------------------------------------------------------------------------------- 1 | export * from './biz' 2 | export * from './transformer' 3 | -------------------------------------------------------------------------------- /packages/base/src/biz-form/transformer.ts: -------------------------------------------------------------------------------- 1 | import type { BizFormItemUnion, RealBiz, ShadowBiz } from './biz' 2 | 3 | /** 4 | * `BizFormItemUnion <-> BizFormItemUnion | BizFormItemUnion[]` 的转换器 5 | * 6 | * 由业务侧定义,表单编辑器组件将在出入口自动调用 7 | */ 8 | export type BizTransformer = { 9 | /** 10 | * `BizFormItemUnion -> BizFormItemUnion | BizFormItemUnion[]` 11 | * 12 | * 表单编辑器输出 items 时调用 13 | * 14 | * @param item BizFormItemUnion 15 | * @param randomStr 随机字符串 16 | */ 17 | toReal: ( 18 | item: BizFormItemUnion, 19 | randomStr: string, 20 | ) => BizFormItemUnion | BizFormItemUnion[] 21 | 22 | /** 23 | * `BizFormItemUnion | BizFormItemUnion[] -> BizFormItemUnion` 24 | * 25 | * 表单编辑器输入 items 时调用 26 | */ 27 | toShadow: ( 28 | itemOrItems: BizFormItemUnion | BizFormItemUnion[], 29 | randomStr: string, 30 | ) => BizFormItemUnion 31 | } 32 | -------------------------------------------------------------------------------- /packages/base/src/env.d.ts: -------------------------------------------------------------------------------- 1 | declare const __ENC_VERSION__: string 2 | -------------------------------------------------------------------------------- /packages/base/src/form-analyzer/FormAnalyzer.ts: -------------------------------------------------------------------------------- 1 | import type { FormItemUnion, PartialFormItemIntersection } from '../form' 2 | 3 | /** 4 | * 表单分析器 5 | */ 6 | export class FormAnalyzer { 7 | private _items: FormItemUnion[] = [] 8 | private _itemMap = new Map() 9 | 10 | constructor(items: FormItemUnion[]) { 11 | this._items = items 12 | this._items.forEach((item) => this._itemMap.set(item.name, item)) 13 | } 14 | 15 | getItems() { 16 | return this._items 17 | } 18 | 19 | findItems(matcher?: PartialFormItemIntersection) { 20 | return this.matchingItems(this._items, matcher) 21 | } 22 | 23 | protected matchingItems( 24 | items: T[], 25 | matcher?: PartialFormItemIntersection, 26 | ) { 27 | if (!matcher) return items 28 | return items.filter((item: any) => 29 | Object.entries(matcher).every(([key, value]) => item[key] === value), 30 | ) 31 | } 32 | 33 | getItem(name: string) { 34 | return this._itemMap.get(name) 35 | } 36 | } 37 | 38 | export function createFormAnalyzer(items: FormItemUnion[]) { 39 | return new FormAnalyzer(items) 40 | } 41 | -------------------------------------------------------------------------------- /packages/base/src/form-analyzer/__tests__/FormAnalyzer.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import type { FormItemUnion } from '../../form' 3 | import { createFormAnalyzer, FormAnalyzer } from '../FormAnalyzer' 4 | 5 | describe('FormAnalyzer', () => { 6 | it('should be defined', () => { 7 | expect(FormAnalyzer).toBeDefined() 8 | expect(createFormAnalyzer).toBeDefined() 9 | expect(createFormAnalyzer([])).toBeInstanceOf(FormAnalyzer) 10 | }) 11 | 12 | const items: FormItemUnion[] = [ 13 | { name: 'name1', label: 'Label1', type: 'input' }, 14 | { name: 'name2', label: 'Label2', type: 'select' }, 15 | { name: 'name3', label: 'Label2', type: 'input', inputType: 'textarea' }, 16 | ] 17 | 18 | it('should be able to getItem', () => { 19 | const formAnalyzer = createFormAnalyzer(items) 20 | 21 | expect(formAnalyzer.getItems()).toBe(items) 22 | expect(formAnalyzer.getItem('name1')).toBe(items[0]) 23 | expect(formAnalyzer.getItem('name2')).toBe(items[1]) 24 | expect(formAnalyzer.getItem('name3')).toBe(items[2]) 25 | }) 26 | 27 | it('should be able to matching items', () => { 28 | const formAnalyzer = createFormAnalyzer(items) 29 | 30 | expect(formAnalyzer.findItems()).toEqual(items) 31 | expect(formAnalyzer.findItems({})).toEqual(items) 32 | expect(formAnalyzer.findItems({ type: 'input' })).toEqual([items[0], items[2]]) 33 | expect(formAnalyzer.findItems({ type: 'select' })).toEqual([items[1]]) 34 | expect(formAnalyzer.findItems({ type: 'input', inputType: 'textarea' })).toEqual([items[2]]) 35 | expect(formAnalyzer.findItems({ inputType: 'textarea' })).toEqual([items[2]]) 36 | expect(formAnalyzer.findItems({ selectMultiple: true })).toEqual([]) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /packages/base/src/form-analyzer/index.ts: -------------------------------------------------------------------------------- 1 | export * from './FormAnalyzer' 2 | export * from './BizFormAnalyzer' 3 | -------------------------------------------------------------------------------- /packages/base/src/form/base.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 基本表单项类型 3 | */ 4 | export interface BaseFormItem { 5 | /** 6 | * 字段名 7 | */ 8 | name: F 9 | /** 10 | * 标签名 11 | */ 12 | label: string 13 | /** 14 | * 控件类型(由 SubFormItem 定义类型收窄) 15 | */ 16 | type: string 17 | /** 18 | * 占位符 19 | */ 20 | placeholder?: string 21 | /** 22 | * 是否隐藏当前项 23 | * @default false 24 | */ 25 | hidden?: boolean 26 | /** 27 | * 只读 28 | * @default false 29 | */ 30 | readonly?: boolean 31 | /** 32 | * 禁用 33 | * @default false 34 | */ 35 | disabled?: boolean 36 | /** 37 | * 验证规则 38 | * @default [] 39 | */ 40 | rules?: FormItemRule[] 41 | /** 42 | * 是否可清空 43 | * @default false 44 | */ 45 | clearable?: boolean 46 | /** 47 | * 列宽/格: 1-24, 默认 24 48 | * 49 | * 例如两个 col 为 12 的控件,会占满同一行 50 | * 51 | * 仅 PC 端有效 52 | * 53 | * @default 24 54 | */ 55 | col?: number 56 | /** 57 | * 宽度缩放比例,即占用的列宽的百分比,范围 0-1 58 | * 59 | * 通常你只需要设置 col,当你的场景是两个表单元素看起来像 col 12,又需要独占一行时 60 | * 可以设置 col 为 24,scale 为 0.5(即宽度收缩为一半) 61 | * 62 | * 仅 PC 端有效 63 | * 64 | * @default 1 65 | */ 66 | scale?: number 67 | /** 68 | * 控件对齐方式(例如 input 内文字,部分控件有效) 69 | * @default 'left' 70 | */ 71 | align?: FormItemAlign 72 | /** 73 | * 额外内容,你可以在此存储一些特定业务场景的东西,比如 select 对应的枚举或字典值,上传地址等 74 | * 75 | * enc 提供的库 API 及组件不负责处理该内容 76 | */ 77 | extra?: E 78 | } 79 | 80 | /** 81 | * 校验规则 82 | */ 83 | export type FormItemRule = { 84 | /** 85 | * 是否必填 86 | */ 87 | required?: boolean 88 | /** 89 | * 正则表达式或正则表达式字符串 90 | * 91 | * 注意,如果传递的是字符串 92 | * /\d+/ 对应为 '\\d+' 93 | */ 94 | pattern?: RegExp | string 95 | /** 96 | * 验证失败的错误消息 97 | */ 98 | message?: string 99 | } 100 | 101 | /** 102 | * 对齐方式(用于 label 和 控件本身) 103 | */ 104 | export type FormItemAlign = 'left' | 'center' | 'right' 105 | -------------------------------------------------------------------------------- /packages/base/src/form/custom.ts: -------------------------------------------------------------------------------- 1 | import type { BaseFormItem } from './base' 2 | 3 | /** 4 | * 自定义表单项 5 | */ 6 | export interface CustomFormItem extends BaseFormItem { 7 | /** 8 | * @override 9 | */ 10 | type: 'custom' 11 | } 12 | -------------------------------------------------------------------------------- /packages/base/src/form/date.ts: -------------------------------------------------------------------------------- 1 | import type { PopupFormItem } from './popup' 2 | 3 | /** 4 | * 日期选择器表单项 5 | */ 6 | export interface DateFormItem extends PopupFormItem { 7 | /** 8 | * @override 9 | */ 10 | type: 'date' 11 | 12 | /** 13 | * 日期选择器类型 14 | * @default 'date' 15 | */ 16 | dateType?: DateFormItemType 17 | /** 18 | * 日期格式化 19 | * @default 'YYYY-MM-DD' 20 | */ 21 | dateFormat?: string 22 | /** 23 | * 日期展示格式化 24 | * 默认跟随 `dateFormat` 25 | */ 26 | dateDisplayFormat?: string 27 | /** 28 | * 可选最小日期 29 | * @default 当前日期的10年前 30 | */ 31 | dateMinDate?: Date | string 32 | /** 33 | * 可选最大日期 34 | * @default 当前日期的10年后 35 | */ 36 | dateMaxDate?: Date | string 37 | /** 38 | * 日期范围分隔符 39 | * 40 | * 仅 `dateType` 为 `"daterange"` 时有效 41 | * @default '-' 42 | */ 43 | dateRangeSeparator?: string 44 | /** 45 | * 日期范围开始占位符 46 | * 47 | * 仅 `dateType` 为 `"daterange"` 时有效 48 | */ 49 | dateRangeStartPlaceholder?: string 50 | /** 51 | * 日期范围结束占位符 52 | * 53 | * 仅 `dateType` 为 `"daterange"` 时有效 54 | */ 55 | dateRangeEndPlaceholder?: string 56 | } 57 | 58 | export type DateFormItemType = 'date' | 'daterange' 59 | -------------------------------------------------------------------------------- /packages/base/src/form/input.ts: -------------------------------------------------------------------------------- 1 | import type { BaseFormItem } from './base' 2 | 3 | /** 4 | * 输入表单项类型 5 | */ 6 | export interface InputFormItem extends BaseFormItem { 7 | /** 8 | * @override 9 | */ 10 | type: 'input' 11 | /** 12 | * 输入框类型 13 | */ 14 | inputType?: InputFormItemType 15 | /** 16 | * 最大长度 17 | */ 18 | inputMaxLength?: number 19 | /** 20 | * 输入框行数,仅 textarea 有效 21 | */ 22 | inputRows?: number 23 | } 24 | 25 | export type InputFormItemType = 'text' | 'password' | 'number' | 'textarea' 26 | -------------------------------------------------------------------------------- /packages/base/src/form/popup.ts: -------------------------------------------------------------------------------- 1 | import type { BaseFormItem } from './base' 2 | 3 | /** 4 | * 弹出层表单项类型 5 | * 6 | * 定义一些弹框配置项,这些配置项通常仅在移动端有效 7 | * 8 | * 所有移动端带弹层的表单项都应该继承该类型 9 | */ 10 | export interface PopupFormItem extends BaseFormItem { 11 | /** 12 | * 是否在点击遮罩层后关闭 13 | * @default true 14 | */ 15 | closeOnClickOverlay?: boolean 16 | /** 17 | * 显示顶部栏 18 | * @default true 19 | */ 20 | showToolbar?: boolean 21 | /** 22 | * 弹出层高度 23 | * @default 'auto' 24 | */ 25 | popupHeight?: string | number 26 | } 27 | -------------------------------------------------------------------------------- /packages/base/src/form/radio.ts: -------------------------------------------------------------------------------- 1 | import { log } from '@cphayim-enc/shared' 2 | 3 | import type { BaseFormItem } from './base' 4 | 5 | /** 6 | * 单选框表单项类型 7 | */ 8 | export interface RadioFormItem extends BaseFormItem { 9 | /** 10 | * @override 11 | */ 12 | type: 'radio' 13 | /** 14 | * 单选框组选项 15 | */ 16 | radioOptions?: (RadioLabelOrValue | RadioOptions)[] 17 | } 18 | 19 | export type RadioLabelOrValue = string | number | boolean 20 | export type RadioOptions = { 21 | /** 22 | * radio 标签右侧文字 23 | */ 24 | label: RadioLabelOrValue 25 | /** 26 | * radio 选中时的值,不存在时取 `label` 的值 27 | */ 28 | value?: RadioLabelOrValue 29 | /** 30 | * 是否禁用该项 31 | */ 32 | disabled?: boolean 33 | } 34 | 35 | export class RadioHelper { 36 | static verifyRadioFormItem(item: RadioFormItem) { 37 | if (!item.radioOptions || !item.radioOptions.length) { 38 | log.warn(`RadioFormItem.radioOptions is empty`) 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/base/src/form/rate.ts: -------------------------------------------------------------------------------- 1 | import type { BaseFormItem } from './base' 2 | 3 | /** 4 | * 评分表单项类型 5 | */ 6 | export interface RateFormItem extends BaseFormItem { 7 | /** 8 | * @override 9 | */ 10 | type: 'rate' 11 | /** 12 | * 评分最大值 13 | * @default 5 14 | */ 15 | rateMax?: number 16 | /** 17 | * 是否允许半选 18 | * @default false 19 | */ 20 | rateAllowHalf?: false 21 | /** 22 | * 选中时颜色 23 | * @default '#F7BA2A' 24 | */ 25 | rateColor?: string 26 | /** 27 | * 未选中时颜色 28 | * @default #C6D1DE 29 | */ 30 | rateVoidColor?: string 31 | /** 32 | * 是否显示文本 33 | * @default false 34 | */ 35 | rateShowText?: boolean 36 | /** 37 | * 显示文本的格式化函数 38 | * @default `(score) => `${score}分`` 39 | */ 40 | rateTextFormatter?: (score: number) => string 41 | /** 42 | * 星星数量对应的文本 43 | * 44 | * 注意: `texts.length` 需要等于 `rateMax` 45 | */ 46 | rateTexts?: string[] 47 | } 48 | -------------------------------------------------------------------------------- /packages/base/src/form/select.ts: -------------------------------------------------------------------------------- 1 | import type { PopupFormItem } from './popup' 2 | 3 | /** 4 | * 选择表单项类型 5 | */ 6 | export interface SelectFormItem extends PopupFormItem { 7 | /** 8 | * @override 9 | */ 10 | type: 'select' 11 | /** 12 | * 是否多选 13 | * @default false 14 | */ 15 | selectMultiple?: boolean 16 | /** 17 | * 多选时最多可选个数,0 为不限制 18 | * @default 0 19 | */ 20 | selectMultipleLimit?: number 21 | /** 22 | * 是否支持搜索过滤 23 | * 24 | * 由 UI package 决定是否支持,如果不支持,将会忽略该配置 25 | */ 26 | selectFilterable?: boolean 27 | /** 28 | * 选择项 29 | * @default [] 30 | */ 31 | selectOptions?: SelectFormItemOption[] 32 | } 33 | 34 | export type SelectFormItemOption = { 35 | /** 36 | * 显示用文本 37 | */ 38 | label: string 39 | /** 40 | * 值 41 | */ 42 | value: string | number | boolean 43 | /** 44 | * 是否禁用 45 | */ 46 | disabled?: boolean 47 | } 48 | -------------------------------------------------------------------------------- /packages/base/src/form/switch.ts: -------------------------------------------------------------------------------- 1 | import type { BaseFormItem } from './base' 2 | 3 | /** 4 | * 开关表单项类型 5 | */ 6 | export interface SwitchFormItem extends BaseFormItem { 7 | /** 8 | * @override 9 | */ 10 | type: 'switch' 11 | /** 12 | * 开值 13 | * @default true 14 | */ 15 | switchActiveValue?: boolean | number | string 16 | /** 17 | * 关值 18 | * @default false 19 | */ 20 | switchInactiveValue?: boolean | number | string 21 | /** 22 | * 打开时的文本 23 | * @default '' 24 | */ 25 | switchActiveText?: string 26 | /** 27 | * 关闭时的文本 28 | * @default '' 29 | */ 30 | switchInactiveText?: string 31 | } 32 | -------------------------------------------------------------------------------- /packages/base/src/form/time.ts: -------------------------------------------------------------------------------- 1 | import type { PopupFormItem } from './popup' 2 | 3 | /** 4 | * 纯时间表单项 5 | */ 6 | export interface TimeFormItem extends PopupFormItem { 7 | /** 8 | * @override 9 | */ 10 | type: 'time' 11 | 12 | /** 13 | * 时间类型 14 | * @default 'hour-minute' 15 | */ 16 | timeType?: TimeFormItemType 17 | } 18 | 19 | export type TimeFormItemType = 'hour-minute' | 'hour-minute-second' 20 | -------------------------------------------------------------------------------- /packages/base/src/index.ts: -------------------------------------------------------------------------------- 1 | export const ENC_VERSION = __ENC_VERSION__ 2 | 3 | export * from './form' 4 | export * from './biz-form' 5 | export * from './form-analyzer' 6 | export * from './utils' 7 | -------------------------------------------------------------------------------- /packages/base/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ImageUtils' 2 | -------------------------------------------------------------------------------- /packages/base/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "include": ["src"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/base/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { type UserConfigExport, defineConfig } from 'vite' 2 | 3 | import { createBuild } from '../../scripts/vite.base.config' 4 | 5 | import { version } from './package.json' 6 | 7 | export default defineConfig(({ mode }) => { 8 | const config: UserConfigExport = { 9 | define: { 10 | __ENC_VERSION__: JSON.stringify(version), 11 | }, 12 | build: createBuild({ mode, root: __dirname }), 13 | plugins: [], 14 | } 15 | 16 | return config 17 | }) 18 | -------------------------------------------------------------------------------- /packages/extension-form-editor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cphayim-enc/extension-form-editor", 3 | "version": "0.3.3", 4 | "description": "", 5 | "keywords": [], 6 | "author": "Cphayim ", 7 | "homepage": "https://github.com/Cphayim/enc#readme", 8 | "license": "ISC", 9 | "type": "module", 10 | "main": "./dist/index.js", 11 | "types": "./dist/index.d.ts", 12 | "exports": { 13 | ".": { 14 | "import": "./dist/index.js", 15 | "require": "./dist/index.cjs", 16 | "types": "./dist/index.d.ts" 17 | } 18 | }, 19 | "files": [ 20 | "dist" 21 | ], 22 | "scripts": { 23 | "dev": "vite build --mode=development --watch", 24 | "build": "vite build && tsc --declaration --emitDeclarationOnly -p tsconfig.build.json", 25 | "lint": "eslint src --ext .ts --fix" 26 | }, 27 | "dependencies": { 28 | "@cphayim-enc/base": "workspace:*", 29 | "@cphayim-enc/shared": "workspace:*" 30 | }, 31 | "publishConfig": { 32 | "access": "public", 33 | "registry": "https://registry.npmjs.org/" 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "git+https://github.com/Cphayim/enc.git", 38 | "directory": "packages/extension-form-editor" 39 | }, 40 | "bugs": { 41 | "url": "https://github.com/Cphayim/enc/issues" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/extension-form-editor/src/biz/__tests__/feature.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { isBizFeature } from '../feature' 3 | 4 | describe('utils', () => { 5 | it('should be able to determine whether it is a BizFormEditorFeature', () => { 6 | expect(isBizFeature(undefined)).toBe(false) 7 | expect(isBizFeature(1)).toBe(false) 8 | expect(isBizFeature({})).toBe(false) 9 | expect(isBizFeature({ bizClass: 1, bizLabel: '1' })).toBe(false) 10 | expect(isBizFeature({ bizClass: 1, bizLabel: '1', bizTransformer: {} })).toBe(false) 11 | expect(isBizFeature({ bizClass: '1', bizLabel: '1', bizTransformer: {} })).toBe(true) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /packages/extension-form-editor/src/biz/feature.ts: -------------------------------------------------------------------------------- 1 | import type { ShadowBiz, BizTransformer } from '@cphayim-enc/base' 2 | import { isNone, isString, isObject } from '@cphayim-enc/shared' 3 | 4 | /** 5 | * 业务表单编辑器功能 6 | */ 7 | export type BizFormEditorFeature = Pick & { 8 | /** 9 | * 业务描述,显示在 bizLabel 下方 10 | */ 11 | bizDesc?: string 12 | /** 13 | * "占位项" <=> "实际项" | "实际项"[] 的转换器 14 | */ 15 | bizTransformer: BizTransformer 16 | /** 17 | * 是否单例(编辑器将阻止该业务控件被添加多次) 18 | * @default false 19 | */ 20 | bizSingleton?: boolean 21 | } 22 | 23 | export function isBizFeature(feature: any): feature is BizFormEditorFeature { 24 | return ( 25 | !isNone(feature) && 26 | isString(feature.bizClass) && 27 | isString(feature.bizLabel) && 28 | isObject(feature.bizTransformer) 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /packages/extension-form-editor/src/biz/index.ts: -------------------------------------------------------------------------------- 1 | export * from './feature' 2 | export * from './transformer' 3 | -------------------------------------------------------------------------------- /packages/extension-form-editor/src/config/index.ts: -------------------------------------------------------------------------------- 1 | import { PresetName } from '../preset' 2 | import type { BizFormEditorFeature } from '../biz' 3 | 4 | /** 5 | * 表单编辑器可用的操作项 6 | */ 7 | export enum FormEditorOperation { 8 | /** 9 | * 确认 10 | */ 11 | Confirm, 12 | /** 13 | * 预览 14 | */ 15 | Preview, 16 | /** 17 | * 打印配置项到控制台 18 | */ 19 | PrintItems, 20 | } 21 | 22 | /** 23 | * 可视化表单编辑器配置 24 | */ 25 | export interface FormEditorConfig { 26 | /** 27 | * 预览用的 `EncForm` 组件(注意不是组件实例) 28 | * 29 | * 你可以传入任何一个 ui-package 所提供的 `EncForm` 组件 30 | */ 31 | encFormComponent?: any 32 | /** 33 | * 需要透传给预览用的 `EncForm` 组件的额外 `props` 34 | */ 35 | encFormProps?: Record 36 | /** 37 | * 启用的操作项 38 | */ 39 | operations?: FormEditorOperation[] 40 | /** 41 | * 启用的预设功能,没有传递则全开 42 | */ 43 | presets?: PresetName[] 44 | /** 45 | * 启用的业务功能 46 | */ 47 | bizFeatures?: BizFormEditorFeature[] 48 | /** 49 | * 仅使用随机字段名 50 | * 51 | * 启用后控件 `name` 不可编辑 52 | */ 53 | randomNameOnly?: boolean 54 | /** 55 | * 随机字段名长度 56 | * 57 | * @default 8 58 | */ 59 | randomNameLength?: number 60 | /** 61 | * formItem 为空时的提示文字 62 | */ 63 | formItemEmptyText?: string 64 | /** 65 | * 是否同步 `options` 中的 `label` 和 `value` 66 | * 67 | * 这会影响到编辑器中所有含选项的配置行为 68 | * - `true`: 禁用选项的 `value` 编辑,自动同步 `label` 的值到 `value` 69 | * - `false`: 可以单独编辑 `label` 和 `value` 70 | * 71 | * @default false 72 | */ 73 | syncOptionsLabelAndValue?: boolean 74 | /** 75 | * 是否为表单编辑器输出的 FormItemUnion 打标记 76 | * - __CREATED_BY_ENC_FORM_EDITOR__: ${ENC_VERSION} 77 | * 78 | * @default true 79 | */ 80 | markItemCreatedByEditor?: boolean 81 | } 82 | 83 | export const DEFAULT_FORM_EDITOR_CONFIG: FormEditorConfig = { 84 | // default enable all 85 | presets: Object.values(PresetName), 86 | bizFeatures: [], 87 | randomNameOnly: false, 88 | randomNameLength: 8, 89 | syncOptionsLabelAndValue: false, 90 | markItemCreatedByEditor: true, 91 | } 92 | -------------------------------------------------------------------------------- /packages/extension-form-editor/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './config' 2 | export * from './preset' 3 | export * from './biz' 4 | export * from './utils' 5 | 6 | export const formEditorTips = { 7 | left: ['点击按钮:向表单尾部添加控件', '拖放按钮:向表单合适位置添加控件'], 8 | center: [ 9 | '拖放控件:调整控件在表单中的顺序', 10 | '点击控件:选中控件进入编辑,右下角按钮可删除控件,右侧栏可编辑属性', 11 | ], 12 | right: ['编辑选中控件的属性,自动保存'], 13 | } 14 | -------------------------------------------------------------------------------- /packages/extension-form-editor/src/preset/__tests__/feature.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | import type { FormItemUnion } from '@cphayim-enc/base' 4 | 5 | import { PresetName, getPresetFeature, isPresetFeature } from '../feature' 6 | 7 | function isFormItem(item: FormItemUnion): item is FormItemUnion { 8 | return ['name', 'label', 'type'].every((key) => Object.hasOwnProperty.call(item, key)) 9 | } 10 | 11 | describe('preset', () => { 12 | it('should get preset feature', () => { 13 | for (const preset of Object.values(PresetName)) { 14 | const presetFeature = getPresetFeature(preset) 15 | expect(presetFeature).toBeTruthy() 16 | expect(isPresetFeature(presetFeature)).toBe(true) 17 | expect(presetFeature.presetName).toBe(preset) 18 | } 19 | }) 20 | 21 | it('should get item by preset', () => { 22 | for (const preset of Object.values(PresetName)) { 23 | const presetFeature = getPresetFeature(preset) 24 | expect(isPresetFeature(presetFeature)).toBe(true) 25 | 26 | const item = presetFeature.getItem('test', 'Test') 27 | expect(isFormItem(item)).toBe(true) 28 | expect(item.name).toBe('test') 29 | expect(item.label).toBe('Test') 30 | } 31 | }) 32 | 33 | it('should be able to determine whether it is a PresetFeature', () => { 34 | expect(isPresetFeature(undefined)).toBe(false) 35 | expect(isPresetFeature(1)).toBe(false) 36 | expect(isPresetFeature({})).toBe(false) 37 | expect(isPresetFeature({ presetName: 1, presetLabel: '1' })).toBe(false) 38 | expect(isPresetFeature({ presetName: 1, presetLabel: '1', getItem: () => void 0 })).toBe(false) 39 | expect(isPresetFeature({ presetName: '1', presetLabel: '1', getItem: () => void 0 })).toBe(true) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /packages/extension-form-editor/src/preset/feature-group.ts: -------------------------------------------------------------------------------- 1 | import { PresetName, getPresetFeature, type PresetFormEditorFeature } from './feature' 2 | 3 | /** 4 | * 表单编辑器预设功能分组 5 | */ 6 | export type PresetFormEditorFeatureGroup = { 7 | /** 8 | * 分组名称 9 | */ 10 | groupName: string 11 | /** 12 | * 预设功能列表 13 | */ 14 | features: PresetFormEditorFeature[] 15 | } 16 | 17 | export const GROUP_NAMES = { 18 | INPUT_TYPE: '输入型控件', 19 | SELECT_TYPE: '选择型控件', 20 | UPLOAD_TYPE: '上传型控件', 21 | } 22 | 23 | /** 24 | * 获取在 presetSet 中启用的预设功能分组列表 25 | */ 26 | export function getPresetFeatureGroups(presetSet: Set): PresetFormEditorFeatureGroup[] { 27 | return [ 28 | { 29 | groupName: GROUP_NAMES.INPUT_TYPE, 30 | features: filterAndGetPresetFeature(presetSet, [ 31 | PresetName.Input, 32 | PresetName.Textarea, 33 | PresetName.Number, 34 | PresetName.Password, 35 | ]), 36 | }, 37 | { 38 | groupName: GROUP_NAMES.SELECT_TYPE, 39 | features: filterAndGetPresetFeature(presetSet, [ 40 | PresetName.Select, 41 | PresetName.Switch, 42 | PresetName.Radio, 43 | PresetName.Checkbox, 44 | PresetName.Date, 45 | PresetName.Time, 46 | PresetName.DateRange, 47 | PresetName.Rate, 48 | ]), 49 | }, 50 | { 51 | groupName: GROUP_NAMES.UPLOAD_TYPE, 52 | features: filterAndGetPresetFeature(presetSet, [ 53 | PresetName.UploadImage, 54 | PresetName.UploadFile, 55 | ]), 56 | }, 57 | ] 58 | } 59 | 60 | export function filterAndGetPresetFeature(presetSet: Set, includes: PresetName[]) { 61 | return includes.filter((preset) => presetSet.has(preset)).map(getPresetFeature) 62 | } 63 | -------------------------------------------------------------------------------- /packages/extension-form-editor/src/preset/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | type PresetFormEditorFeature, 3 | PresetName, 4 | getPresetFeature, 5 | isPresetFeature, 6 | } from './feature' 7 | 8 | export { type PresetFormEditorFeatureGroup, getPresetFeatureGroups } from './feature-group' 9 | -------------------------------------------------------------------------------- /packages/extension-form-editor/src/utils/__tests__/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { ENC_VERSION, FormItemUnion } from '@cphayim-enc/base' 2 | import { describe, expect, it } from 'vitest' 3 | 4 | import { markItemsCreatedByEditor } from '..' 5 | 6 | describe('utils', () => { 7 | it('should be add a mark to items', () => { 8 | const items: FormItemUnion[] = [ 9 | { name: 'a', label: 'A', type: 'custom' }, 10 | { name: 'b', label: 'B', type: 'custom' }, 11 | ] 12 | const markedItems = markItemsCreatedByEditor(items) 13 | markedItems.forEach((item) => { 14 | expect(item).toMatchObject({ __CREATED_BY_ENC_FORM_EDITOR__: `v${ENC_VERSION}` }) 15 | }) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /packages/extension-form-editor/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { ENC_VERSION, type FormItemUnion } from '@cphayim-enc/base' 2 | 3 | /** 4 | * 为表单编辑器创建的 FormItemUnion 打标 5 | */ 6 | export function markItemCreatedByEditor(item: FormItemUnion): FormItemUnion { 7 | return { 8 | ...item, 9 | __CREATED_BY_ENC_FORM_EDITOR__: `v${ENC_VERSION}`, 10 | } as any 11 | } 12 | 13 | export function markItemsCreatedByEditor(items: FormItemUnion[]) { 14 | return items.map(markItemCreatedByEditor) 15 | } 16 | -------------------------------------------------------------------------------- /packages/extension-form-editor/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "include": ["src"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/extension-form-editor/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { type UserConfigExport, defineConfig } from 'vite' 2 | 3 | import { createBuild } from '../../scripts/vite.base.config' 4 | 5 | export default defineConfig(({ mode }) => { 6 | const config: UserConfigExport = { 7 | build: createBuild({ mode, root: __dirname }), 8 | plugins: [], 9 | } 10 | 11 | return config 12 | }) 13 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@ombro/eslint-config-vue3-typescript'], 4 | rules: { 5 | 'prefer-const': 'off', 6 | 'vue/multi-word-component-names': 'off', 7 | 'vue/valid-v-model': 'off', 8 | '@typescript-eslint/no-empty-interface': 'off', 9 | '@typescript-eslint/no-non-null-assertion': 'off', 10 | '@typescript-eslint/no-explicit-any': 'off', 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cphayim-enc/extension-vue-form-editor", 3 | "version": "0.3.3", 4 | "description": "", 5 | "keywords": [], 6 | "author": "Cphayim ", 7 | "homepage": "https://github.com/Cphayim/enc#readme", 8 | "license": "ISC", 9 | "type": "module", 10 | "main": "./dist/index.js", 11 | "types": "./dist/index.d.ts", 12 | "exports": { 13 | ".": { 14 | "import": "./dist/index.js", 15 | "require": "./dist/index.cjs", 16 | "types": "./dist/index.d.ts" 17 | }, 18 | "./style": { 19 | "import": "./dist/style.js", 20 | "require": "./dist/style.cjs", 21 | "types": "./dist/style.d.ts" 22 | } 23 | }, 24 | "files": [ 25 | "dist" 26 | ], 27 | "scripts": { 28 | "dev": "vite build --mode=development --watch", 29 | "build": "vite build && vue-tsc --declaration --emitDeclarationOnly -p tsconfig.build.json", 30 | "lint": "eslint src --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix" 31 | }, 32 | "dependencies": { 33 | "@cphayim-enc/base": "workspace:*", 34 | "@cphayim-enc/extension-form-editor": "workspace:*", 35 | "@cphayim-enc/shared": "workspace:*", 36 | "@cphayim-enc/vue": "workspace:*", 37 | "@ombro/dnd-backend": "^0.1.6", 38 | "@ombro/dnd-vue": "^0.1.6" 39 | }, 40 | "devDependencies": { 41 | "@vueuse/core": "^10.7.0", 42 | "vue": "^3.3.11", 43 | "vue-router": "^4.2.5" 44 | }, 45 | "peerDependencies": { 46 | "@vueuse/core": ">=8.0.0", 47 | "vue": "^3.2.25", 48 | "vue-router": "^4.0.0" 49 | }, 50 | "peerDependenciesMeta": {}, 51 | "publishConfig": { 52 | "access": "public", 53 | "registry": "https://registry.npmjs.org/" 54 | }, 55 | "repository": { 56 | "type": "git", 57 | "url": "git+https://github.com/Cphayim/enc.git", 58 | "directory": "packages/extension-vue-form-editor" 59 | }, 60 | "bugs": { 61 | "url": "https://github.com/Cphayim/enc/issues" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const { baseWithTailwind } = require('../../scripts/postcss.base.config.cjs') 2 | 3 | module.exports = { 4 | ...baseWithTailwind, 5 | } 6 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/__tests__/mock-enc-form.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue' 2 | 3 | export type CreateTestMockEncFormOptions = { 4 | validate?: () => Promise 5 | clearValidate?: (names?: string | string[]) => Promise 6 | getValues?: () => Record 7 | } 8 | 9 | export const createTestMockEncForm = ({ 10 | validate = async () => undefined, 11 | clearValidate = async () => undefined, 12 | getValues = () => ({}), 13 | }: CreateTestMockEncFormOptions = {}) => { 14 | return defineComponent({ 15 | name: 'EncForm', 16 | props: { 17 | data: Object, 18 | items: Array, 19 | }, 20 | setup(props, { emit }) { 21 | const updateValueByName = (name: string, value: any) => { 22 | emit('update:data', { 23 | ...props.data, 24 | [name]: value, 25 | }) 26 | } 27 | return { 28 | validate, 29 | clearValidate, 30 | getValues, 31 | updateValueByName, // for test 32 | } 33 | }, 34 | template: '
', 35 | }) 36 | } 37 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/fieldset/Fieldset.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/fieldset/__tests__/Fieldset.spec.tsx: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { mount } from '@vue/test-utils' 3 | 4 | import { EncFieldset } from '..' 5 | 6 | describe('Fieldset.vue', () => { 7 | it('should render fieldset and legend', () => { 8 | const wrapper = mount(() => ) 9 | expect(wrapper.find('fieldset').exists()).toBe(true) 10 | expect(wrapper.find('legend').exists()).toBe(true) 11 | expect(wrapper.find('legend').text()).toBe('标题') 12 | }) 13 | 14 | it('should be able to render the default slot', () => { 15 | const wrapper = mount(() => ( 16 | 17 |
child
18 |
19 | )) 20 | expect(wrapper.find('.child').exists()).toBe(true) 21 | expect(wrapper.find('.child').text()).toBe('child') 22 | }) 23 | 24 | it('should be able to render the title slot', () => { 25 | const wrapper = mount(() => ( 26 |
child
, 30 | title: () =>
title
, 31 | }} 32 | >
33 | )) 34 | 35 | expect(wrapper.find('.child').exists()).toBe(true) 36 | expect(wrapper.find('.child').text()).toBe('child') 37 | 38 | expect(wrapper.find('legend').exists()).toBe(true) 39 | expect(wrapper.find('legend').text()).not.contain('标题') 40 | expect(wrapper.find('.title').exists()).toBe(true) 41 | expect(wrapper.find('.title').text()).toBe('title') 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/fieldset/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | 3 | import Fieldset from './Fieldset.vue' 4 | 5 | export const EncFieldset = withInstall(Fieldset) 6 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-edit-panel/center-panel/CenterPanel.vue: -------------------------------------------------------------------------------- 1 | 2 | 24 | 25 | 47 | 48 | 53 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-edit-panel/center-panel/EmptyContainer.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 47 | 48 | 55 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-edit-panel/center-panel/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import CenterPanel from './CenterPanel.vue' 3 | 4 | export const EncCenterPanel = withInstall(CenterPanel) 5 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-edit-panel/dnd.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | BizFormEditorFeature, 3 | PresetFormEditorFeature, 4 | } from '@cphayim-enc/extension-form-editor' 5 | 6 | // 枚举无法使用 Symbol 7 | export const DnDTypes = { 8 | Feature: Symbol('feature'), // 左侧控件栏 9 | Item: Symbol('item'), // 表单项 10 | } 11 | 12 | export interface DragItem { 13 | type: 'item' 14 | index: number // 被拖动项的索引 15 | } 16 | export interface DragFeature { 17 | type: 'feature' 18 | feature: PresetFormEditorFeature | BizFormEditorFeature // 被拖动的控件功能(新增) 19 | currentIndex?: number // 当前保存位置的索引 20 | } 21 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-edit-panel/index.ts: -------------------------------------------------------------------------------- 1 | import { type Emitter, type FormItemUnion, withInstall } from '@cphayim-enc/vue' 2 | import type { 3 | PresetFormEditorFeature, 4 | BizFormEditorFeature, 5 | } from '@cphayim-enc/extension-form-editor' 6 | 7 | import FormEditPanel from './FormEditPanel.vue' 8 | 9 | export const EncFormEditPanel = withInstall(FormEditPanel) 10 | 11 | export type FormEditorSelectedItem = { 12 | type: string 13 | index: number 14 | item: FormItemUnion 15 | } & ( 16 | | { type: 'select' } 17 | | { 18 | type: 'adding' | 'removing' 19 | callback?: () => void // 取消选中后的回调 20 | } 21 | ) 22 | 23 | export type FormEditorInternalEvents = { 24 | 'add-item': { item: FormItemUnion; index: number; select?: boolean } 25 | 'remove-item': { index: number; select?: boolean } 26 | 'move-item': { oldIndex: number; newIndex: number } 27 | 'add-item-by-feature': { 28 | feature: PresetFormEditorFeature | BizFormEditorFeature 29 | index?: number // 添加的位置,默认为尾部 30 | oldIndex?: number // 先前的位置,需要移除 31 | } 32 | // 'select': 选中状态 33 | // 'add': 添加的选中状态(显示添加效果),后续取消选中 34 | // 'remove': 移除的选中状态(显示移除效果),后续取消选中 35 | // 'unselect': 取消选中状态 36 | 'select-item': FormEditorSelectedItem | { type: 'unselect' } 37 | } 38 | 39 | export type FormEditorInternalEmitter = Emitter 40 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-edit-panel/left-panel/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import LeftPanel from './LeftPanel.vue' 3 | 4 | export const EncLeftPanel = withInstall(LeftPanel) 5 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-edit-panel/right-panel/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import RightPanel from './RightPanel.vue' 3 | 4 | export const EncRightPanel = withInstall(RightPanel) 5 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-editor-tip/FormEditorTip.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 16 | 22 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-editor-tip/__tests__/FormEditorTip.spec.tsx: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import { describe, expect, it } from 'vitest' 3 | import { EncFormEditorTip } from '..' 4 | 5 | describe('FormEditorTip.vue', () => { 6 | it('should render single tip', () => { 7 | const wrapper = mount(() => ) 8 | expect(wrapper.findAll('li').length).toBe(1) 9 | expect(wrapper.find('li').text()).toBe('提示内容提示内容') 10 | }) 11 | 12 | it('should render multiple tips', () => { 13 | const wrapper = mount(() => ( 14 | 15 | )) 16 | const tips = wrapper.findAll('li') 17 | expect(tips.length).toBe(3) 18 | for (let i = 0; i < tips.length; i++) { 19 | expect(tips[i].text()).toBe(`提示内容${i + 1}`) 20 | } 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-editor-tip/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | 3 | import FormEditorTip from './FormEditorTip.vue' 4 | 5 | export const EncFormEditorTip = withInstall(FormEditorTip) 6 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-editor/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | 3 | import FormEditor from './FormEditor.vue' 4 | 5 | export const EncFormEditor = withInstall(FormEditor) 6 | export type EncFormEditorInstanceType = InstanceType 7 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/base/BaseFormItemEditor.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/base/__tests__/BaseFormItemEditor.spec.tsx: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { mount } from '@vue/test-utils' 3 | import { ref } from 'vue' 4 | 5 | import type { BaseFormItem } from '@cphayim-enc/base' 6 | import type { FormEditorConfig } from '@cphayim-enc/extension-form-editor' 7 | 8 | import { createTestMockEncForm } from '../../../../__tests__/mock-enc-form' 9 | import { BASE_ITEMS } from '../items' 10 | import { EncBaseFormItemEditor } from '..' 11 | 12 | const createItem = () => { 13 | return ref({ 14 | name: 'test', 15 | label: 'Test', 16 | type: 'input', 17 | }) 18 | } 19 | 20 | const createConfig = (randomNameOnly = false): FormEditorConfig => { 21 | return { 22 | mode: 'visual', 23 | randomNameOnly, 24 | encFormComponent: createTestMockEncForm(), 25 | } 26 | } 27 | 28 | describe('BaseFormItemEditor.vue', () => { 29 | it('should render EncForm', () => { 30 | const modelValue = createItem() 31 | const wrapper = mount(() => ( 32 | 33 | )) 34 | 35 | const encForm = wrapper.findComponent({ name: 'EncForm' }) 36 | expect(encForm.exists()).toBe(true) 37 | 38 | expect(encForm.props().data).toBeDefined() 39 | expect(encForm.props().items).toBeDefined() 40 | 41 | encForm.vm.updateValueByName('label', 'Test2') 42 | expect(modelValue.value.label).toBe('Test2') 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/base/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | 3 | import BaseFormItemEditor from './BaseFormItemEditor.vue' 4 | 5 | export const EncBaseFormItemEditor = withInstall(BaseFormItemEditor) 6 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/base/items.ts: -------------------------------------------------------------------------------- 1 | import type { FormItemUnion } from '@cphayim-enc/base' 2 | 3 | export const BASE_ITEMS: FormItemUnion[] = [ 4 | { 5 | name: 'name', 6 | label: '字段名', 7 | type: 'input', 8 | placeholder: '表单字段名', 9 | rules: [{ required: true, message: '必填项' }], 10 | }, 11 | { 12 | name: 'label', 13 | label: '标签名', 14 | type: 'input', 15 | placeholder: '表单标签名,显示用', 16 | rules: [{ required: true, message: '必填项' }], 17 | }, 18 | { 19 | name: 'placeholder', 20 | label: '占位符', 21 | type: 'input', 22 | placeholder: '例:"请输入 XXX"', 23 | }, 24 | { 25 | name: 'col', 26 | label: '列宽度', 27 | type: 'select', 28 | selectOptions: [ 29 | { label: '25%', value: 6 }, 30 | { label: '50%', value: 12 }, 31 | { label: '100%', value: 24 }, 32 | ], 33 | }, 34 | { 35 | name: 'hidden', 36 | label: '隐藏', 37 | type: 'checkbox', 38 | col: 8, 39 | }, 40 | { 41 | name: 'readonly', 42 | label: '只读', 43 | type: 'checkbox', 44 | col: 8, 45 | }, 46 | { 47 | name: 'disabled', 48 | label: '禁用', 49 | type: 'checkbox', 50 | col: 8, 51 | }, 52 | ] 53 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/checkbox/CheckboxFormItemOptionEditor.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/checkbox/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | 3 | import CheckboxFormItemEditor from './CheckboxFormItemEditor.vue' 4 | 5 | export const EncCheckboxFormItemEditor = withInstall(CheckboxFormItemEditor) 6 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/checkbox/items.ts: -------------------------------------------------------------------------------- 1 | import type { FormItemUnion } from '@cphayim-enc/base' 2 | 3 | export const CHECKBOX_ITEMS: FormItemUnion[] = [ 4 | { 5 | name: 'checkboxType', 6 | label: '多选类型', 7 | type: 'select', 8 | selectOptions: [ 9 | { label: '单独', value: 'single' }, 10 | { label: '分组', value: 'group' }, 11 | ], 12 | }, 13 | { 14 | name: 'checkboxSingleLabel', 15 | label: '单独文本', 16 | type: 'input', 17 | }, 18 | { 19 | name: 'checkboxSingleTrueValue', 20 | label: '选中值', 21 | type: 'input', 22 | }, 23 | { 24 | name: 'checkboxSingleFalseValue', 25 | label: '未选中值', 26 | type: 'input', 27 | }, 28 | { 29 | name: 'checkboxGroupMax', 30 | label: '分组限制', 31 | type: 'input', 32 | inputType: 'number', 33 | }, 34 | ] 35 | 36 | export const CHECKBOX_OPTION_ITEMS: FormItemUnion[] = [ 37 | { 38 | name: 'label', 39 | label: '文本', 40 | type: 'input', 41 | col: 8, 42 | }, 43 | { 44 | name: 'value', 45 | label: '值', 46 | type: 'input', 47 | col: 8, 48 | }, 49 | { 50 | name: 'disabled', 51 | label: '禁用', 52 | type: 'checkbox', 53 | col: 8, 54 | }, 55 | ] 56 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/common.ts: -------------------------------------------------------------------------------- 1 | export const COMMON_ENC_FORM_PROPS = { 2 | labelWidth: 70, 3 | rowGutter: 0, 4 | size: 'small', 5 | disablePadding: true, 6 | } 7 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/date/DateFormItemEditor.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/date/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | 3 | import DateFormItemEditor from './DateFormItemEditor.vue' 4 | 5 | export const EncDateFormItemEditor = withInstall(DateFormItemEditor) 6 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/date/items.ts: -------------------------------------------------------------------------------- 1 | import type { FormItemUnion } from '@cphayim-enc/base' 2 | 3 | export const DATE_ITEMS: FormItemUnion[] = [ 4 | { 5 | name: 'dateType', 6 | label: '日期类型', 7 | type: 'select', 8 | selectOptions: [ 9 | { label: '日期', value: 'date' }, 10 | { label: '日期范围', value: 'daterange' }, 11 | ], 12 | }, 13 | { 14 | name: 'dateFormat', 15 | label: '日期格式', 16 | type: 'input', 17 | }, 18 | { 19 | name: 'dateMinDate', 20 | label: '最小日期', 21 | type: 'date', 22 | dateType: 'date', 23 | }, 24 | { 25 | name: 'dateMaxDate', 26 | label: '最大日期', 27 | type: 'date', 28 | dateType: 'date', 29 | }, 30 | { 31 | name: 'dateRangeSeparator', 32 | label: '分隔符', 33 | type: 'input', 34 | }, 35 | { 36 | name: 'dateRangeStartPlaceholder', 37 | label: '开始占位', 38 | type: 'input', 39 | }, 40 | { 41 | name: 'dateRangeEndPlaceholder', 42 | label: '结束占位', 43 | type: 'input', 44 | }, 45 | ] 46 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/index.ts: -------------------------------------------------------------------------------- 1 | import type { FormItemUnion } from '@cphayim-enc/base' 2 | 3 | import { EncCheckboxFormItemEditor } from './checkbox' 4 | import { EncDateFormItemEditor } from './date' 5 | import { EncInputFormItemEditor } from './input' 6 | import { EncRadioFormItemEditor } from './radio' 7 | import { EncRateFormItemEditor } from './rate' 8 | import { EncSelectFormItemEditor } from './select' 9 | import { EncSwitchFormItemEditor } from './switch' 10 | import { EncTimeFormItemEditor } from './time' 11 | import { EncUploadFormItemEditor } from './upload' 12 | 13 | // formItem.type -> FormItemEditor 的映射关系 14 | export const typeFormItemEditorComponentMap: Record = { 15 | input: EncInputFormItemEditor, 16 | select: EncSelectFormItemEditor, 17 | cascader: null, 18 | date: EncDateFormItemEditor, 19 | time: EncTimeFormItemEditor, 20 | upload: EncUploadFormItemEditor, 21 | switch: EncSwitchFormItemEditor, 22 | radio: EncRadioFormItemEditor, 23 | checkbox: EncCheckboxFormItemEditor, 24 | rate: EncRateFormItemEditor, 25 | custom: null, 26 | } 27 | 28 | export * from './base' 29 | export * from './rules' 30 | export * from './input' 31 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/input/InputFormItemEditor.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/input/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | 3 | import InputFormItemEditor from './InputFormItemEditor.vue' 4 | 5 | export const EncInputFormItemEditor = withInstall(InputFormItemEditor) 6 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/input/items.ts: -------------------------------------------------------------------------------- 1 | import type { FormItemUnion } from '@cphayim-enc/base' 2 | 3 | export const INPUT_ITEMS: FormItemUnion[] = [ 4 | { 5 | name: 'inputType', 6 | label: '输入类型', 7 | type: 'select', 8 | selectOptions: [ 9 | { label: '单行输入', value: 'text' }, 10 | { label: '多行输入', value: 'textarea' }, 11 | { label: '数字输入', value: 'number' }, 12 | { label: '密码输入', value: 'password' }, 13 | ], 14 | rules: [{ required: true, message: '必填项' }], 15 | }, 16 | { 17 | name: 'inputRows', 18 | label: '显示行数', 19 | type: 'input', 20 | inputType: 'number', 21 | hidden: true, 22 | }, 23 | { 24 | name: 'inputMaxLength', 25 | label: '长度限制', 26 | type: 'input', 27 | inputType: 'number', 28 | }, 29 | ] 30 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/radio/RadioFormItemOptionEditor.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/radio/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | 3 | import RadioFormItemEditor from './RadioFormItemEditor.vue' 4 | 5 | export const EncRadioFormItemEditor = withInstall(RadioFormItemEditor) 6 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/radio/items.ts: -------------------------------------------------------------------------------- 1 | import type { FormItemUnion } from '@cphayim-enc/base' 2 | 3 | export const RADIO_ITEMS: FormItemUnion[] = [] 4 | 5 | export const RADIO_OPTION_ITEMS: FormItemUnion[] = [ 6 | { 7 | name: 'label', 8 | label: '文本', 9 | type: 'input', 10 | col: 8, 11 | }, 12 | { 13 | name: 'value', 14 | label: '值', 15 | type: 'input', 16 | col: 8, 17 | }, 18 | { 19 | name: 'disabled', 20 | label: '禁用', 21 | type: 'checkbox', 22 | col: 8, 23 | }, 24 | ] 25 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/rate/RateFormItemEditor.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/rate/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | 3 | import RateFormItemEditor from './RateFormItemEditor.vue' 4 | 5 | export const EncRateFormItemEditor = withInstall(RateFormItemEditor) 6 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/rate/items.ts: -------------------------------------------------------------------------------- 1 | import type { FormItemUnion } from '@cphayim-enc/base' 2 | 3 | export const RATE_ITEMS: FormItemUnion[] = [ 4 | { 5 | name: 'rateMax', 6 | label: '最大分值', 7 | type: 'input', 8 | inputType: 'number', 9 | }, 10 | ] 11 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/rules/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | 3 | import RulesFormItemEditor from './RulesFormItemEditor.vue' 4 | 5 | export const EncRulesFormItemEditor = withInstall(RulesFormItemEditor) 6 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/rules/items.ts: -------------------------------------------------------------------------------- 1 | import type { FormItemUnion } from '@cphayim-enc/base' 2 | 3 | export const REQUIRED_RULE_ITEMS: FormItemUnion[] = [ 4 | { name: 'required', label: '必填', type: 'checkbox', col: 6 }, 5 | { name: 'message', label: '必填提示', type: 'input', placeholder: '例:"必填项"', col: 18 }, 6 | ] 7 | 8 | export const PATTERN_RULE_ITEMS: FormItemUnion[] = [ 9 | { 10 | name: 'pattern', 11 | label: '校验正则', 12 | type: 'input', 13 | placeholder: '正则表达式,例:^\\d+$', 14 | }, 15 | { name: 'message', label: '校验提示', type: 'input', placeholder: '例:"必须满足 XXX 规则"' }, 16 | ] 17 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/select/SelectFormItemOptionEditor.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/select/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | 3 | import SelectFormItemEditor from './SelectFormItemEditor.vue' 4 | import SelectFormItemOptionEditor from './SelectFormItemOptionEditor.vue' 5 | 6 | export const EncSelectFormItemEditor = withInstall(SelectFormItemEditor) 7 | export const EncSelectFormItemOptionEditor = withInstall(SelectFormItemOptionEditor) 8 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/select/items.ts: -------------------------------------------------------------------------------- 1 | import type { FormItemUnion } from '@cphayim-enc/base' 2 | 3 | export const SELECT_ITEMS: FormItemUnion[] = [ 4 | { 5 | name: 'selectMultiple', 6 | label: '是否多选', 7 | type: 'checkbox', 8 | col: 12, 9 | }, 10 | { 11 | name: 'selectFilterable', 12 | label: '条件过滤', 13 | type: 'checkbox', 14 | col: 12, 15 | }, 16 | { 17 | name: 'selectMultipleLimit', 18 | label: '多选数量', 19 | type: 'input', 20 | inputType: 'number', 21 | hidden: true, 22 | }, 23 | ] 24 | 25 | export const SELECT_OPTION_ITEMS: FormItemUnion[] = [ 26 | { 27 | name: 'label', 28 | label: '文本', 29 | type: 'input', 30 | col: 8, 31 | }, 32 | { 33 | name: 'value', 34 | label: '值', 35 | type: 'input', 36 | col: 8, 37 | }, 38 | { 39 | name: 'disabled', 40 | label: '禁用', 41 | type: 'checkbox', 42 | col: 8, 43 | }, 44 | ] 45 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/switch/SwitchFormItemEditor.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/switch/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | 3 | import SwitchFormItemEditor from './SwitchFormItemEditor.vue' 4 | 5 | export const EncSwitchFormItemEditor = withInstall(SwitchFormItemEditor) 6 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/switch/items.ts: -------------------------------------------------------------------------------- 1 | import type { FormItemUnion } from '@cphayim-enc/base' 2 | 3 | export const SWITCH_ITEMS: FormItemUnion[] = [ 4 | { 5 | name: 'switchActiveValue', 6 | label: '开启值', 7 | type: 'input', 8 | col: 12, 9 | }, 10 | { 11 | name: 'switchInactiveValue', 12 | label: '关闭值', 13 | type: 'input', 14 | col: 12, 15 | }, 16 | { 17 | name: 'switchActiveText', 18 | label: '开启文本', 19 | type: 'input', 20 | col: 12, 21 | }, 22 | { 23 | name: 'switchInactiveText', 24 | label: '关闭文本', 25 | type: 'input', 26 | col: 12, 27 | }, 28 | ] 29 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/time/TimeFormItemEditor.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/time/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | 3 | import TimeFormItemEditor from './TimeFormItemEditor.vue' 4 | 5 | export const EncTimeFormItemEditor = withInstall(TimeFormItemEditor) 6 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/time/items.ts: -------------------------------------------------------------------------------- 1 | import type { FormItemUnion } from '@cphayim-enc/base' 2 | 3 | export const TIME_ITEMS: FormItemUnion[] = [ 4 | { 5 | name: 'timeType', 6 | label: '时间类型', 7 | type: 'select', 8 | selectOptions: [ 9 | { label: '时:分', value: 'hour-minute' }, 10 | { label: '时:分:秒', value: 'hour-minute-second' }, 11 | ], 12 | }, 13 | ] 14 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/upload/UploadFormItemEditor.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/upload/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | 3 | import UploadFormItemEditor from './UploadFormItemEditor.vue' 4 | 5 | export const EncUploadFormItemEditor = withInstall(UploadFormItemEditor) 6 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-item-editor/upload/items.ts: -------------------------------------------------------------------------------- 1 | import type { FormItemUnion } from '@cphayim-enc/base' 2 | 3 | export const UPLOAD_ITEMS: FormItemUnion[] = [ 4 | { 5 | name: 'uploadType', 6 | label: '上传类型', 7 | type: 'select', 8 | selectOptions: [ 9 | { label: '图片', value: 'image' }, 10 | { label: '文件', value: 'file' }, 11 | ], 12 | }, 13 | { 14 | name: 'uploadTips', 15 | label: '提示文字', 16 | type: 'input', 17 | }, 18 | { 19 | name: 'uploadButtonText', 20 | label: '按钮文字', 21 | type: 'input', 22 | }, 23 | { 24 | name: 'uploadMultiple', 25 | label: '多文件', 26 | type: 'checkbox', 27 | }, 28 | { 29 | name: 'uploadMultipleLimit', 30 | label: '数量限制', 31 | type: 'input', 32 | inputType: 'number', 33 | }, 34 | ] 35 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-preview/FormPreview.vue: -------------------------------------------------------------------------------- 1 | 43 | 44 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-preview/__tests__/FormPreview.spec.tsx: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { mount } from '@vue/test-utils' 3 | import { defineComponent } from 'vue' 4 | 5 | import { EncFormPreview } from '..' 6 | 7 | describe('FormPreview.vue', () => { 8 | const EncForm = defineComponent({ 9 | name: 'EncForm', 10 | props: ['data', 'items'], 11 | template: '
EncForm
', 12 | }) 13 | 14 | it('should be render preview', () => { 15 | const wrapper = mount(() => ) 16 | const encForm = wrapper.findComponent({ name: 'EncForm' }) 17 | expect(encForm.exists()).toBe(true) 18 | expect(wrapper.text()).toContain('EncForm') 19 | 20 | // pass the necessary props (data, items) to EncForm 21 | expect(encForm.props('data')).toBeDefined() 22 | expect(encForm.props('items')).toBeDefined() 23 | }) 24 | 25 | it('should be throw error when encFormComponent is not a encForm component', () => { 26 | expect(() => mount(() => )).toThrow( 27 | 'props encFormComponent must be a EncForm component', 28 | ) 29 | 30 | const NotEncForm = defineComponent({ 31 | name: 'NotEncForm', 32 | template: '
NotEncForm
', 33 | }) 34 | expect(() => mount(() => )).toThrow( 35 | 'props encFormComponent must be a EncForm component', 36 | ) 37 | }) 38 | 39 | it('should be able to pass extra props to EncForm', () => { 40 | const wrapper = mount(() => ( 41 | 42 | )) 43 | const encForm = wrapper.findComponent({ name: 'EncForm' }) 44 | 45 | expect(encForm.attributes('extra')).toBe('extra') 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/form-preview/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import FormPreview from './FormPreview.vue' 3 | 4 | export const EncFormPreview = withInstall(FormPreview) 5 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './form-editor' 2 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/hooks/__tests__/use-editor-items.spec.tsx: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest' 2 | import { mount } from '@vue/test-utils' 3 | import { defineComponent, type PropType, ref } from 'vue' 4 | 5 | import type { FormEditorConfig } from '@cphayim-enc/extension-form-editor' 6 | 7 | import { createTestMockEncForm } from '../../__tests__/mock-enc-form' 8 | import { useEditorItems } from '../use-editor-items' 9 | 10 | const Comp = defineComponent({ 11 | props: { 12 | config: { 13 | type: Object as PropType, 14 | required: true, 15 | }, 16 | }, 17 | setup(props) { 18 | const data = ref({}) 19 | const { EncForm, formItems, formRef } = useEditorItems([], props.config) 20 | return { 21 | EncForm, 22 | formItems, 23 | data, 24 | formRef, 25 | } 26 | }, 27 | render() { 28 | const { EncForm, formItems, data } = this 29 | return 30 | }, 31 | }) 32 | 33 | describe('useEditorItems', () => { 34 | it('should be throw error when not passed config.encFormComponent', () => { 35 | expect(() => mount(Comp, { props: { config: {} } })).toThrow( 36 | 'props encFormComponent must be a EncForm component', 37 | ) 38 | }) 39 | 40 | it('should be render EncForm', () => { 41 | const wrapper = mount(() => ) 42 | expect(wrapper.findComponent({ name: 'EncForm' }).exists()).toBe(true) 43 | expect(wrapper.findComponent({ name: 'EncForm' }).vm.data).toEqual({}) 44 | expect(wrapper.findComponent({ name: 'EncForm' }).vm.items).toEqual([]) 45 | }) 46 | 47 | it('should be call encForm.validate on mounted', () => { 48 | const validate = vi.fn() 49 | const wrapper = mount(() => ( 50 | 51 | )) 52 | expect(wrapper.findComponent({ name: 'EncForm' }).exists()).toBe(true) 53 | expect(validate).toBeCalled() 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './use-editor-items' 2 | export * from './use-sync-editor-options-items' 3 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/hooks/use-editor-items.ts: -------------------------------------------------------------------------------- 1 | import { computed, onMounted, ref } from 'vue' 2 | 3 | import type { FormItemUnion } from '@cphayim-enc/base' 4 | import { createErrorMessage } from '@cphayim-enc/shared' 5 | import { useFormItems } from '@cphayim-enc/vue' 6 | import type { FormEditorConfig } from '@cphayim-enc/extension-form-editor' 7 | 8 | /** 9 | * 表单编辑器表单项 10 | */ 11 | export function useEditorItems(items: FormItemUnion[], config: FormEditorConfig) { 12 | const EncForm = computed(() => { 13 | if (config.encFormComponent?.name !== 'EncForm') 14 | throw Error(createErrorMessage('props encFormComponent must be a EncForm component')) 15 | return config.encFormComponent 16 | }) 17 | 18 | const formRef = ref() 19 | // 立即执行一次验证,此处不抛出异常 20 | onMounted(() => { 21 | try { 22 | formRef.value?.validate?.() 23 | } catch (error) {} 24 | }) 25 | 26 | return { EncForm, formRef, ...useFormItems(items) } 27 | } 28 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/hooks/use-sync-editor-options-items.ts: -------------------------------------------------------------------------------- 1 | import { type Ref, watchEffect, type WritableComputedRef } from 'vue' 2 | 3 | import type { 4 | CascaderFormItemOption, 5 | CheckboxOptions, 6 | FormItemUnion, 7 | RadioOptions, 8 | SelectFormItemOption, 9 | } from '@cphayim-enc/base' 10 | import type { FormEditorConfig } from '@cphayim-enc/extension-form-editor' 11 | 12 | import { useEditorItems } from './use-editor-items' 13 | 14 | type OptionUnion = 15 | | undefined 16 | | SelectFormItemOption 17 | | CascaderFormItemOption 18 | | RadioOptions 19 | | CheckboxOptions 20 | 21 | /** 22 | * Options 表单编辑器表单项 23 | * 24 | * 仅用于 Select, Radio, Checkbox 的 Options 配置项的 items 25 | */ 26 | export function useSyncOptionsEditorItems( 27 | items: FormItemUnion[], 28 | config: FormEditorConfig, 29 | modelValue: Ref | WritableComputedRef, 30 | ) { 31 | const rest = useEditorItems(items, config) 32 | 33 | const { updateItem } = rest 34 | 35 | // 开启键值同步时更新 items,并同步值 36 | watchEffect(() => { 37 | updateItem('label', { 38 | col: config.syncOptionsLabelAndValue ? 16 : 8, 39 | }) 40 | updateItem('value', { 41 | disabled: config.syncOptionsLabelAndValue, 42 | hidden: config.syncOptionsLabelAndValue, 43 | }) 44 | }) 45 | watchEffect(() => { 46 | if (modelValue.value && config.syncOptionsLabelAndValue) { 47 | modelValue.value.value = modelValue.value.label 48 | } 49 | }) 50 | 51 | return { ...rest } 52 | } 53 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/icons/Icon.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 18 | 19 | 28 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/icons/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | 3 | import Icon from './Icon.vue' 4 | 5 | export const EncFormEditorIcon = withInstall(Icon) 6 | 7 | export enum FormEditorIcon { 8 | Remove = 'remove', 9 | Add = 'add', 10 | Close = 'close', 11 | } 12 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { App, Plugin } from 'vue' 2 | 3 | import { type CreateEncOptions, usingSFCWithInstall } from '@cphayim-enc/vue' 4 | 5 | import './icons/iconfont.js' 6 | 7 | import './styles' 8 | import * as componentMap from './components' 9 | 10 | export const createEncExtensionFormEditor = ({ 11 | skipEncInstall = false, 12 | }: CreateEncOptions = {}): Plugin => { 13 | return { 14 | install(app: App) { 15 | if (!skipEncInstall) { 16 | usingSFCWithInstall(app, componentMap) 17 | } 18 | }, 19 | } 20 | } 21 | 22 | export default createEncExtensionFormEditor() 23 | 24 | export * from '@cphayim-enc/extension-form-editor' 25 | export * from './components' 26 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/styles/base.css: -------------------------------------------------------------------------------- 1 | /* enable tailwindcss */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | 6 | :root { 7 | } 8 | 9 | [data-col-span='1'] { 10 | @apply enc-w-1/24; 11 | } 12 | [data-col-span='2'] { 13 | @apply enc-w-2/24; 14 | } 15 | [data-col-span='3'] { 16 | @apply enc-w-3/24; 17 | } 18 | [data-col-span='4'] { 19 | @apply enc-w-4/24; 20 | } 21 | [data-col-span='5'] { 22 | @apply enc-w-5/24; 23 | } 24 | [data-col-span='6'] { 25 | @apply enc-w-6/24; 26 | } 27 | [data-col-span='7'] { 28 | @apply enc-w-7/24; 29 | } 30 | [data-col-span='8'] { 31 | @apply enc-w-8/24; 32 | } 33 | [data-col-span='9'] { 34 | @apply enc-w-9/24; 35 | } 36 | [data-col-span='10'] { 37 | @apply enc-w-10/24; 38 | } 39 | [data-col-span='11'] { 40 | @apply enc-w-11/24; 41 | } 42 | [data-col-span='12'] { 43 | @apply enc-w-12/24; 44 | } 45 | [data-col-span='13'] { 46 | @apply enc-w-13/24; 47 | } 48 | [data-col-span='14'] { 49 | @apply enc-w-14/24; 50 | } 51 | [data-col-span='15'] { 52 | @apply enc-w-15/24; 53 | } 54 | [data-col-span='16'] { 55 | @apply enc-w-16/24; 56 | } 57 | [data-col-span='17'] { 58 | @apply enc-w-17/24; 59 | } 60 | [data-col-span='18'] { 61 | @apply enc-w-18/24; 62 | } 63 | [data-col-span='19'] { 64 | @apply enc-w-19/24; 65 | } 66 | [data-col-span='20'] { 67 | @apply enc-w-20/24; 68 | } 69 | [data-col-span='21'] { 70 | @apply enc-w-21/24; 71 | } 72 | [data-col-span='22'] { 73 | @apply enc-w-22/24; 74 | } 75 | [data-col-span='23'] { 76 | @apply enc-w-23/24; 77 | } 78 | [data-col-span='24'] { 79 | @apply enc-w-24/24; 80 | } 81 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/src/styles/index.ts: -------------------------------------------------------------------------------- 1 | // Third-party non-component styles 2 | 3 | // base styles 4 | import './base.css' 5 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | const plugin = require('tailwindcss/plugin') 2 | const base = require('../../scripts/tailwind.base.config.cjs') 3 | 4 | const columns = {} 5 | for (let i = 1; i <= 24; i++) { 6 | const key = `${i}/24` 7 | columns[key] = `${(100 / 24) * i}%` 8 | } 9 | 10 | module.exports = { 11 | ...base, 12 | theme: { 13 | extend: { 14 | gridTemplateColumns: { 15 | // 24 column grid 16 | 24: 'repeat(24, minmax(0, 1fr))', 17 | }, 18 | width: columns, 19 | }, 20 | }, 21 | plugins: [ 22 | plugin(function ({ addUtilities }) { 23 | addUtilities({ 24 | '.clickable': { 25 | '@apply enc-cursor-pointer active:enc-brightness-75': {}, 26 | }, 27 | }) 28 | }), 29 | ], 30 | } 31 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "include": ["src"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/extension-vue-form-editor/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { UserConfigExport, defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import vueJsx from '@vitejs/plugin-vue-jsx' 4 | import defineOptions from 'unplugin-vue-define-options/vite' 5 | 6 | import { createBuild, genStylePlugin } from '../../scripts/vite.base.config' 7 | 8 | export default defineConfig(({ mode }) => { 9 | const config: UserConfigExport = { 10 | build: createBuild({ 11 | mode, 12 | root: __dirname, 13 | external: ['vue', 'vue-router', '@vueuse/core'], 14 | }), 15 | plugins: [ 16 | vue(), 17 | vueJsx(), 18 | defineOptions(), 19 | genStylePlugin({ preImports: ['@cphayim-enc/vue/style'] }), 20 | ], 21 | } 22 | 23 | return config 24 | }) 25 | -------------------------------------------------------------------------------- /packages/react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cphayim-enc/react", 3 | "version": "0.3.3", 4 | "description": "", 5 | "keywords": [], 6 | "author": "Cphayim ", 7 | "homepage": "https://github.com/Cphayim/enc#readme", 8 | "license": "ISC", 9 | "type": "module", 10 | "main": "./dist/index.js", 11 | "types": "./dist/index.d.ts", 12 | "exports": { 13 | ".": { 14 | "import": "./dist/index.js", 15 | "require": "./dist/index.cjs", 16 | "types": "./dist/index.d.ts" 17 | } 18 | }, 19 | "files": [ 20 | "dist" 21 | ], 22 | "scripts": { 23 | "test": "echo \"Error: no test specified\" && exit 1" 24 | }, 25 | "dependencies": { 26 | "@cphayim-enc/shared": "workspace:*" 27 | }, 28 | "peerDependenciesMeta": {}, 29 | "publishConfig": { 30 | "access": "public", 31 | "registry": "https://registry.npmjs.org/" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/Cphayim/enc.git", 36 | "directory": "packages/react" 37 | }, 38 | "bugs": { 39 | "url": "https://github.com/Cphayim/enc/issues" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/react/src/index.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | -------------------------------------------------------------------------------- /packages/react/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "dist", 5 | "rootDir": "src" 6 | }, 7 | "include": ["src"], 8 | "references": [ 9 | { 10 | "path": "../shared" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/shared/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cphayim-enc/shared", 3 | "version": "0.3.3", 4 | "description": "", 5 | "keywords": [], 6 | "author": "Cphayim ", 7 | "homepage": "https://github.com/Cphayim/enc#readme", 8 | "license": "ISC", 9 | "type": "module", 10 | "main": "./dist/index.js", 11 | "types": "./dist/index.d.ts", 12 | "exports": { 13 | ".": { 14 | "import": "./dist/index.js", 15 | "require": "./dist/index.cjs", 16 | "types": "./dist/index.d.ts" 17 | } 18 | }, 19 | "files": [ 20 | "dist" 21 | ], 22 | "scripts": { 23 | "dev": "vite build --mode=development --watch", 24 | "build": "vite build && tsc --declaration --emitDeclarationOnly -p tsconfig.build.json", 25 | "lint": "eslint src --ext .ts --fix", 26 | "test": "echo \"Error: no test specified\" && exit 1" 27 | }, 28 | "publishConfig": { 29 | "access": "public", 30 | "registry": "https://registry.npmjs.org/" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "git+https://github.com/Cphayim/enc.git", 35 | "directory": "packages/shared" 36 | }, 37 | "bugs": { 38 | "url": "https://github.com/Cphayim/enc/issues" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/shared/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './types' 2 | export * from './type-utils' 3 | 4 | export const PKG_NAME = 'enc' 5 | 6 | const LOG_PREFIX = `[${PKG_NAME}]` 7 | 8 | export const log = { 9 | debug: (...arg: unknown[]) => console.debug(LOG_PREFIX, ...arg), 10 | info: (...arg: unknown[]) => console.info(LOG_PREFIX, ...arg), 11 | warn: (...arg: unknown[]) => console.warn(LOG_PREFIX, ...arg), 12 | error: (...arg: unknown[]) => console.error(LOG_PREFIX, ...arg), 13 | } 14 | 15 | export function createErrorMessage(message: string) { 16 | return `${LOG_PREFIX} ${message}` 17 | } 18 | 19 | export function createThrowErrorFunction(message: string) { 20 | return () => { 21 | throw new Error(createErrorMessage(message)) 22 | } 23 | } 24 | 25 | export function sleep(ms: number) { 26 | return new Promise((resolve) => setTimeout(resolve, ms)) 27 | } 28 | 29 | export async function delayWrapper(fn: () => T, delay: number) { 30 | return new Promise((resolve) => { 31 | setTimeout(() => { 32 | resolve(fn()) 33 | }, delay) 34 | }) 35 | } 36 | 37 | export function randomStr(length: number) { 38 | length = length || 32 39 | let t = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678', 40 | a = t.length, 41 | n = '' 42 | for (let i = 0; i < length; i++) n += t.charAt(Math.floor(Math.random() * a)) 43 | return n 44 | } 45 | 46 | export function deepClone(obj: T): T { 47 | // issue: Proxy objects have problems with structuredClone 48 | // return typeof structuredClone !== 'undefined' 49 | // ? structuredClone(obj) 50 | // : JSON.parse(JSON.stringify(obj)) 51 | 52 | return JSON.parse(JSON.stringify(obj)) 53 | } 54 | 55 | export function getFileNameFromUrl(url: string) { 56 | return url.split('#')[0].split('?')[0].split('/').pop() 57 | } 58 | -------------------------------------------------------------------------------- /packages/shared/src/type-utils.ts: -------------------------------------------------------------------------------- 1 | const getType = (o: unknown): string => Object.prototype.toString.call(o).slice(8, -1) 2 | 3 | export const isObject = (o: unknown): o is Record => getType(o) === 'Object' 4 | 5 | export const isArray = Array.isArray 6 | 7 | export const isSet = (o: unknown): o is Set => getType(o) === 'Set' 8 | 9 | export const isMap = (o: unknown): o is Map => getType(o) === 'Map' 10 | 11 | export const isFunction = (o: unknown): o is (...args: never) => unknown => 12 | getType(o) === 'Function' 13 | 14 | export const isAsyncFunction = (o: unknown): o is (...args: never) => Promise => 15 | getType(o) === 'AsyncFunction' 16 | 17 | export const isNumber = (o: unknown): o is number => getType(o) === 'Number' 18 | 19 | export const isInt = (o: unknown): o is number => isNumber(o) && Math.floor(o) === o 20 | 21 | export const isString = (o: unknown): o is string => getType(o) === 'String' 22 | 23 | export const isEmptyString = (o: unknown): o is string => isString(o) && o.trim().length === 0 24 | 25 | export const isBoolean = (o: unknown): o is boolean => getType(o) === 'Boolean' 26 | 27 | export const isUndefined = (o: unknown): o is undefined => getType(o) === 'Undefined' 28 | 29 | export const isNull = (o: unknown): o is null => getType(o) === 'Null' 30 | 31 | export const isUndefinedOrNull = (o: unknown): o is undefined | null => isUndefined(o) || isNull(o) 32 | 33 | export const isNone = isUndefinedOrNull 34 | 35 | export const isSymbol = (o: unknown): o is symbol => getType(o) === 'Symbol' 36 | 37 | export const isBigInt = (o: unknown): o is bigint => getType(o) === 'BigInt' 38 | 39 | export const isRegExp = (o: unknown): o is RegExp => getType(o) === 'RegExp' 40 | 41 | export function toArray(value: T | T[]): T[] { 42 | return Array.isArray(value) ? value : [value] 43 | } 44 | -------------------------------------------------------------------------------- /packages/shared/src/types.ts: -------------------------------------------------------------------------------- 1 | export type IsEmptyObject> = [keyof Obj] extends [never] 2 | ? true 3 | : false 4 | 5 | export type IsNotEmptyObject> = 6 | IsEmptyObject extends true ? false : true 7 | 8 | /** 9 | * 元组转联合 10 | */ 11 | export type TupleToUnion = T[number] 12 | 13 | /** 14 | * 对元组的每一项 Omit 15 | */ 16 | export type TupleOmit = { 17 | [P in keyof T]: Omit 18 | } 19 | 20 | /** 21 | * 联合转交叉 22 | */ 23 | export type UnionToIntersection = (U extends any ? (k: U) => void : never) extends ( 24 | k: infer I, 25 | ) => void 26 | ? I 27 | : never 28 | 29 | /** 30 | * 对联合类型进行 Pick,并将 Pick 的字段组成联合类型 31 | * 32 | * @example 33 | * ```ts 34 | * PickInUnion<{ type: 'a', a1: number } | { type: 'b', b1: string }, 'type'> // { type: 'a' | 'b' } 35 | * ``` 36 | */ 37 | export type PickInUnion = Pick, F> 38 | -------------------------------------------------------------------------------- /packages/shared/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "include": ["src"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/shared/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { UserConfigExport, defineConfig } from 'vite' 2 | 3 | import { createBuild } from '../../scripts/vite.base.config' 4 | 5 | export default defineConfig(({ mode }) => { 6 | const config: UserConfigExport = { 7 | build: createBuild({ mode, root: __dirname }), 8 | plugins: [], 9 | } 10 | 11 | return config 12 | }) 13 | -------------------------------------------------------------------------------- /packages/style/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cphayim-enc/style", 3 | "version": "0.3.3", 4 | "description": "", 5 | "keywords": [], 6 | "author": "Cphayim ", 7 | "homepage": "https://github.com/Cphayim/enc#readme", 8 | "license": "ISC", 9 | "type": "module", 10 | "main": "./dist/index.js", 11 | "types": "./dist/index.d.ts", 12 | "exports": { 13 | ".": { 14 | "import": "./dist/index.js", 15 | "require": "./dist/index.cjs", 16 | "types": "./dist/index.d.ts" 17 | }, 18 | "./style": { 19 | "import": "./dist/style.js", 20 | "require": "./dist/style.cjs", 21 | "types": "./dist/style.d.ts" 22 | } 23 | }, 24 | "files": [ 25 | "dist" 26 | ], 27 | "scripts": { 28 | "dev": "vite build --mode=development --watch", 29 | "build": "vite build && tsc --declaration --emitDeclarationOnly -p tsconfig.build.json", 30 | "lint": "eslint src --ext .ts --fix" 31 | }, 32 | "dependencies": {}, 33 | "peerDependenciesMeta": {}, 34 | "publishConfig": { 35 | "access": "public", 36 | "registry": "https://registry.npmjs.org/" 37 | }, 38 | "repository": { 39 | "type": "git", 40 | "url": "git+https://github.com/Cphayim/enc.git", 41 | "directory": "packages/style" 42 | }, 43 | "bugs": { 44 | "url": "https://github.com/Cphayim/enc/issues" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/style/src/index.ts: -------------------------------------------------------------------------------- 1 | import './style.css' 2 | 3 | export enum EncCSSVariables { 4 | // colors 5 | BackgroundColor = '--enc-background-color', 6 | PrimaryColor = '--enc-primary-color', 7 | 8 | // transition / animation 9 | TransitionRate = '--enc-transition-rate', 10 | TransitionDuration = '--enc-transition-duration', 11 | TransitionTimingFunction = '--enc-transition-timing-function', 12 | AnimationRate = '--enc-animation-rate', 13 | AnimationDuration = '--enc-animation-duration', 14 | AnimationTimingFunction = '--enc-animation-timing-function', 15 | 16 | // safe area 17 | SafeAreaInsetTop = '--enc-safe-area-inset-top', 18 | SafeAreaInsetBottom = '--enc-safe-area-inset-bottom', 19 | SafeAreaInsetLeft = '--enc-safe-area-inset-left', 20 | SafeAreaInsetRight = '--enc-safe-area-inset-right', 21 | 22 | // scrollbar 23 | ScrollbarSize = '--enc-scrollbar-size', 24 | ScrollbarColor = '--enc-scrollbar-color', 25 | ScrollbarHoverColor = '--enc-scrollbar-hover-color', 26 | ScrollbarInactiveColor = '--enc-scrollbar-inactive-color', 27 | ScrollbarBorderWidth = '--enc-scrollbar-border-width', 28 | ScrollbarBorderStyle = '--enc-scrollbar-border-style', 29 | ScrollbarBorderColor = '--enc-scrollbar-border-color', 30 | ScrollbarBorderRadius = '--enc-scrollbar-border-radius', 31 | ScrollbarTrackColor = '--enc-scrollbar-track-color', 32 | } 33 | -------------------------------------------------------------------------------- /packages/style/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "include": ["src"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/style/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { type UserConfigExport, defineConfig } from 'vite' 2 | 3 | import { createBuild, genStylePlugin } from '../../scripts/vite.base.config' 4 | 5 | export default defineConfig(({ mode }) => { 6 | const config: UserConfigExport = { 7 | build: createBuild({ mode, root: __dirname }), 8 | plugins: [genStylePlugin()], 9 | } 10 | 11 | return config 12 | }) 13 | -------------------------------------------------------------------------------- /packages/test-utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cphayim-enc/test-utils", 3 | "private": true, 4 | "description": "", 5 | "keywords": [], 6 | "license": "ISC", 7 | "type": "module", 8 | "main": "src/index.ts" 9 | } 10 | -------------------------------------------------------------------------------- /packages/test-utils/src/index.ts: -------------------------------------------------------------------------------- 1 | import { vi } from 'vitest' 2 | 3 | export function createConsoleSpy() { 4 | return { 5 | log: vi.spyOn(console, 'log').mockImplementation(() => void 0), 6 | debug: vi.spyOn(console, 'debug').mockImplementation(() => void 0), 7 | info: vi.spyOn(console, 'info').mockImplementation(() => void 0), 8 | warn: vi.spyOn(console, 'warn').mockImplementation(() => void 0), 9 | error: vi.spyOn(console, 'error').mockImplementation(() => void 0), 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/vue-element-plus/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@ombro/eslint-config-vue3-typescript'], 4 | rules: { 5 | 'prefer-const': 'off', 6 | 'vue/multi-word-component-names': 'off', 7 | '@typescript-eslint/no-empty-interface': 'off', 8 | '@typescript-eslint/no-non-null-assertion': 'off', 9 | '@typescript-eslint/no-explicit-any': 'off', 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /packages/vue-element-plus/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cphayim-enc/vue-element-plus", 3 | "version": "0.3.3", 4 | "description": "", 5 | "keywords": [], 6 | "author": "Cphayim ", 7 | "homepage": "https://github.com/Cphayim/enc#readme", 8 | "license": "ISC", 9 | "type": "module", 10 | "main": "./dist/index.js", 11 | "types": "./dist/index.d.ts", 12 | "exports": { 13 | ".": { 14 | "import": "./dist/index.js", 15 | "require": "./dist/index.cjs", 16 | "types": "./dist/index.d.ts" 17 | }, 18 | "./style": { 19 | "import": "./dist/style.js", 20 | "require": "./dist/style.cjs", 21 | "types": "./dist/style.d.ts" 22 | } 23 | }, 24 | "files": [ 25 | "dist" 26 | ], 27 | "scripts": { 28 | "dev": "vite build --mode=development --watch", 29 | "build": "vite build && vue-tsc --declaration --emitDeclarationOnly -p tsconfig.build.json", 30 | "lint": "eslint src --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix", 31 | "test": "echo \"Error: no test specified\" && exit 1" 32 | }, 33 | "dependencies": { 34 | "@cphayim-enc/base": "workspace:*", 35 | "@cphayim-enc/shared": "workspace:*", 36 | "@cphayim-enc/style": "workspace:*", 37 | "@cphayim-enc/vue": "workspace:*" 38 | }, 39 | "devDependencies": { 40 | "@element-plus/icons-vue": "^2.3.1", 41 | "@vueuse/core": "^10.7.0", 42 | "dayjs": "^1.11.10", 43 | "element-plus": "^2.4.3", 44 | "vue": "^3.3.11" 45 | }, 46 | "peerDependencies": { 47 | "@element-plus/icons-vue": "^2.0.0", 48 | "@vueuse/core": ">=8.0.0", 49 | "element-plus": "^2.2.0", 50 | "vue": "^3.2.25" 51 | }, 52 | "peerDependenciesMeta": {}, 53 | "publishConfig": { 54 | "access": "public", 55 | "registry": "https://registry.npmjs.org/" 56 | }, 57 | "repository": { 58 | "type": "git", 59 | "url": "git+https://github.com/Cphayim/enc.git", 60 | "directory": "packages/vue-element-plus" 61 | }, 62 | "bugs": { 63 | "url": "https://github.com/Cphayim/enc/issues" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/vue-element-plus/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const { baseWithTailwind } = require('../../scripts/postcss.base.config.cjs') 2 | 3 | module.exports = { 4 | ...baseWithTailwind, 5 | } 6 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/components/form/cascader/CascaderFormItem.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/components/form/cascader/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import CascaderFormItem from './CascaderFormItem.vue' 3 | 4 | export const EncCascaderFormItem = withInstall(CascaderFormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/components/form/checkbox/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import CheckboxFormItem from './CheckboxFormItem.vue' 3 | 4 | export const EncCheckboxFormItem = withInstall(CheckboxFormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/components/form/date/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import DateFormItem from './DateFormItem.vue' 3 | 4 | export const EncDateFormItem = withInstall(DateFormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/components/form/form-item/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import FormItem from './FormItem.vue' 3 | 4 | export const EncFormItem = withInstall(FormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/components/form/form/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import Form from './Form.vue' 3 | 4 | export const EncForm = withInstall(Form) 5 | export type EncFormInstanceType = InstanceType 6 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/components/form/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cascader' 2 | export * from './checkbox' 3 | export * from './date' 4 | export * from './form' 5 | export * from './form-item' 6 | export * from './input' 7 | export * from './radio' 8 | export * from './rate' 9 | export * from './select' 10 | export * from './switch' 11 | export * from './time' 12 | export * from './upload' 13 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/components/form/input/InputFormItem.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/components/form/input/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import InputFormItem from './InputFormItem.vue' 3 | 4 | export const EncInputFormItem = withInstall(InputFormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/components/form/radio/RadioFormItem.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/components/form/radio/__tests__/RadioFormItem.spec.tsx: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { mount } from '@vue/test-utils' 3 | import { nextTick, ref } from 'vue' 4 | import { ElRadio, ElRadioGroup } from 'element-plus' 5 | 6 | import type { RadioFormItem } from '@cphayim-enc/base' 7 | import { EncRadioFormItem } from '..' 8 | 9 | describe('RadioFormItem.vue', () => { 10 | const DEFAULT_ITEM: RadioFormItem = { 11 | type: 'radio', 12 | name: 'tags', 13 | label: 'Tags', 14 | radioOptions: [ 15 | { label: 'Tag 1', value: 'tag1' }, 16 | { label: 'Tag 2', value: 'tag2' }, 17 | 'Tag3', // 18 | ], 19 | } 20 | 21 | it('should able be to sync value with radio group', async () => { 22 | const modelValue = ref() 23 | const item = ref({ ...DEFAULT_ITEM }) 24 | 25 | const wrapper = mount(() => ) 26 | 27 | const elRadioGroupRef = wrapper.findComponent(ElRadioGroup) 28 | expect(elRadioGroupRef.exists()).toBe(true) 29 | 30 | const elRadioRefs = elRadioGroupRef.findAllComponents(ElRadio) 31 | expect(elRadioRefs.length).toBe(3) 32 | 33 | modelValue.value = 'tag1' 34 | await nextTick() 35 | expect(elRadioRefs[0].classes()).toContain('is-checked') 36 | 37 | await elRadioRefs[1].find('input').setValue(true) 38 | expect(modelValue.value).toBe('tag2') 39 | expect(elRadioRefs[0].classes()).not.toContain('is-checked') 40 | expect(elRadioRefs[1].classes()).toContain('is-checked') 41 | expect(elRadioRefs[2].classes()).not.toContain('is-checked') 42 | 43 | await elRadioRefs[2].find('input').setValue(true) 44 | expect(modelValue.value).toBe('Tag3') 45 | expect(elRadioRefs[0].classes()).not.toContain('is-checked') 46 | expect(elRadioRefs[1].classes()).not.toContain('is-checked') 47 | expect(elRadioRefs[2].classes()).toContain('is-checked') 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/components/form/radio/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import RadioFormItem from './RadioFormItem.vue' 3 | 4 | export const EncRadioFormItem = withInstall(RadioFormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/components/form/rate/RateFormItem.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/components/form/rate/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import RateFormItem from './RateFormItem.vue' 3 | 4 | export const EncRateFormItem = withInstall(RateFormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/components/form/select/SelectFormItem.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/components/form/select/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import SelectFormItem from './SelectFormItem.vue' 3 | 4 | export const EncSelectFormItem = withInstall(SelectFormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/components/form/switch/SwitchFormItem.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/components/form/switch/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import SwitchFormItem from './SwitchFormItem.vue' 3 | 4 | export const EncSwitchFormItem = withInstall(SwitchFormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/components/form/time/TimeFormItem.vue: -------------------------------------------------------------------------------- 1 | 50 | 51 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/components/form/time/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import TimeFormItem from './TimeFormItem.vue' 3 | 4 | export const EncTimeFormItem = withInstall(TimeFormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/components/form/upload/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import UploadFormItem from './UploadFormItem.vue' 3 | 4 | export const EncUploadFormItem = withInstall(UploadFormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './form' 2 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/hooks/__tests__/use-confirm.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest' 2 | 3 | const confirmFn = vi.fn() 4 | 5 | vi.mock('element-plus', async () => { 6 | return { 7 | ElMessageBox: { 8 | confirm: confirmFn, 9 | }, 10 | } 11 | }) 12 | 13 | const { useConfirm } = await import('../use-confirm') 14 | 15 | describe('useLoading', async () => { 16 | it('should use ElMessageBox', async () => { 17 | const onConfirm = vi.fn() 18 | const onCancel = vi.fn() 19 | const wrappedFn = useConfirm({ 20 | message: 'test message', 21 | title: 'test title', 22 | confirmText: 'test confirm text', 23 | cancelText: 'test cancel text', 24 | onConfirm, 25 | onCancel, 26 | }) 27 | await wrappedFn('123') 28 | expect(confirmFn).toBeCalledTimes(1) 29 | expect(confirmFn.mock.calls[0][0]).toMatchObject('test message') 30 | expect(confirmFn.mock.calls[0][1]).toMatchObject('test title') 31 | expect(confirmFn.mock.calls[0][2]).toMatchObject({ 32 | showConfirmButton: true, 33 | showCancelButton: true, 34 | confirmButtonText: 'test confirm text', 35 | cancelButtonText: 'test cancel text', 36 | }) 37 | expect(onConfirm).toBeCalledTimes(1) 38 | expect(onConfirm).toBeCalledWith('123') 39 | expect(onCancel).toBeCalledTimes(0) 40 | 41 | confirmFn.mockImplementationOnce(() => Promise.reject()) 42 | await wrappedFn('456') 43 | expect(confirmFn).toBeCalledTimes(2) 44 | expect(confirmFn.mock.calls[1][0]).toMatchObject('test message') 45 | expect(confirmFn.mock.calls[1][1]).toMatchObject('test title') 46 | expect(confirmFn.mock.calls[1][2]).toMatchObject({ 47 | showConfirmButton: true, 48 | showCancelButton: true, 49 | confirmButtonText: 'test confirm text', 50 | cancelButtonText: 'test cancel text', 51 | }) 52 | expect(onConfirm).toBeCalledTimes(1) 53 | expect(onCancel).toBeCalledTimes(1) 54 | expect(onCancel).toBeCalledWith('456') 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/hooks/__tests__/use-form.spec.tsx: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import { describe, expect, it } from 'vitest' 3 | 4 | import { EncForm } from '../../components' 5 | import { useForm } from '../use-form' 6 | 7 | describe('useForm', () => { 8 | it('should be exists extra properties', () => { 9 | const { formRef, setFormRef } = useForm({}, []) 10 | expect(formRef).toBeDefined() 11 | expect(setFormRef).toBeDefined() 12 | }) 13 | 14 | it('should be set formRef', () => { 15 | const { formData, formItems, formRef, setFormRef } = useForm({}, []) 16 | expect(formRef.value).toBeUndefined() 17 | 18 | mount(() => ) 19 | expect(formRef.value).toBeDefined() 20 | 21 | setFormRef(undefined) 22 | expect(formRef.value).toBeUndefined() 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/hooks/__tests__/use-loading.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest' 2 | import { createConsoleSpy } from '@cphayim-enc/test-utils' 3 | 4 | const closeFn = vi.fn() 5 | const ElMessageSpy = vi.fn((_: any) => ({ close: closeFn })) 6 | 7 | vi.mock('element-plus', async () => { 8 | const module = await vi.importActual('element-plus') 9 | 10 | return { 11 | ...module, 12 | ElMessage: ElMessageSpy, 13 | } 14 | }) 15 | 16 | const { useLoading } = await import('../use-loading') 17 | 18 | describe('useLoading', async () => { 19 | it('should use ElMessage on success', async () => { 20 | createConsoleSpy() 21 | 22 | const fn = vi.fn() 23 | const wrappedFn = useLoading(fn, { 24 | message: 'test loading', 25 | successMessage: 'test success', 26 | errorMessage: 'test error', 27 | }) 28 | await wrappedFn() 29 | expect(fn).toHaveBeenCalled() 30 | expect(ElMessageSpy).toBeCalledTimes(2) // loading + success 31 | expect(ElMessageSpy.mock.calls[0][0]).toMatchObject({ message: 'test loading', type: 'info' }) 32 | expect(closeFn).toHaveBeenCalled() 33 | expect(ElMessageSpy.mock.calls[1][0]).toMatchObject({ 34 | message: 'test success', 35 | type: 'success', 36 | }) 37 | }) 38 | 39 | it('should use ElMessage on error', async () => { 40 | createConsoleSpy() 41 | 42 | const fn = vi.fn(() => { 43 | throw new Error() 44 | }) 45 | const wrappedFn = useLoading(fn, { 46 | message: 'test loading', 47 | successMessage: 'test success', 48 | errorMessage: 'test error', 49 | }) 50 | await wrappedFn() 51 | expect(fn).toHaveBeenCalled() 52 | expect(ElMessageSpy).toBeCalledTimes(2) // loading + error 53 | expect(ElMessageSpy.mock.calls[0][0]).toMatchObject({ message: 'test loading', type: 'info' }) 54 | expect(closeFn).toHaveBeenCalled() 55 | expect(ElMessageSpy.mock.calls[1][0]).toMatchObject({ 56 | message: 'test error', 57 | type: 'error', 58 | }) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './use-loading' 2 | export * from './use-confirm' 3 | 4 | export * from './use-form' 5 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/hooks/use-confirm.ts: -------------------------------------------------------------------------------- 1 | import { ElMessageBox } from 'element-plus' 2 | import 'element-plus/es/components/message-box/style/css' 3 | import { useConfirm as _useConfirm, type UseConfirmOptions } from '@cphayim-enc/vue' 4 | 5 | export type { UseConfirmOptions } 6 | 7 | export function useConfirm( 8 | options?: UseConfirmOptions, 9 | ): (...args: T) => Promise { 10 | return _useConfirm({ 11 | confirmor: async (args) => { 12 | try { 13 | await ElMessageBox.confirm(args.message, args.title, { 14 | type: 'info', 15 | confirmButtonText: args.confirmText, 16 | cancelButtonText: args.cancelText, 17 | showConfirmButton: true, 18 | showCancelButton: true, 19 | }) 20 | return true 21 | } catch (error) { 22 | return false 23 | } 24 | }, 25 | ...options, 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/hooks/use-form.ts: -------------------------------------------------------------------------------- 1 | import { type Ref, ref } from 'vue' 2 | 3 | import type { FormItemUnion } from '@cphayim-enc/base' 4 | import { useForm as _useForm, type UseFormOptions } from '@cphayim-enc/vue' 5 | import type { EncFormInstanceType } from '../components' 6 | 7 | type EncFormInstance = EncFormInstanceType | undefined 8 | 9 | /** 10 | * 对等表单 useForm hooks 11 | */ 12 | export function useForm, F = Extract>( 13 | initData: T, 14 | items: FormItemUnion[] | Ref[]>, 15 | options?: UseFormOptions, 16 | ) { 17 | const formRef = ref() 18 | const setFormRef = (instance: EncFormInstance) => { 19 | formRef.value = instance 20 | } 21 | 22 | return { 23 | ..._useForm(initData, items, options), 24 | formRef, 25 | setFormRef, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/hooks/use-loading.ts: -------------------------------------------------------------------------------- 1 | import { ElMessage, type MessageHandler } from 'element-plus' 2 | import 'element-plus/es/components/message/style/css' 3 | import { Loading } from '@element-plus/icons-vue' 4 | 5 | import { useLoading as _useLoading } from '@cphayim-enc/vue' 6 | import type { UseLoadingOptions } from '@cphayim-enc/vue' 7 | 8 | const DEFAULT_DURATION = 3000 9 | 10 | export function useLoading( 11 | fn: (...args: T) => void | Promise, 12 | options?: UseLoadingOptions, 13 | ): (...args: T) => Promise { 14 | return _useLoading(fn, { 15 | onLoading: (message) => { 16 | return ElMessage({ type: 'info', message, duration: 0, icon: Loading }) 17 | }, 18 | onClearLoading: (flag: MessageHandler) => { 19 | flag.close() 20 | }, 21 | onSuccess: (message) => { 22 | ElMessage({ type: 'success', message, duration: options?.duration ?? DEFAULT_DURATION }) 23 | }, 24 | onError: (message) => { 25 | ElMessage({ type: 'error', message, duration: options?.duration ?? DEFAULT_DURATION }) 26 | }, 27 | ...options, 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { App, Plugin } from 'vue' 2 | 3 | import { type CreateEncOptions, usingSFCWithInstall } from '@cphayim-enc/vue' 4 | 5 | import './style.css' 6 | import * as componentMap from './components' 7 | 8 | export const createEncElementPlus = ({ skipEncInstall = false }: CreateEncOptions = {}): Plugin => { 9 | return { 10 | install(app: App) { 11 | if (!skipEncInstall) { 12 | usingSFCWithInstall(app, componentMap) 13 | } 14 | }, 15 | } 16 | } 17 | 18 | export default createEncElementPlus() 19 | 20 | export * from './upstream' 21 | export * from './hooks' 22 | export * from './components' 23 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/style.css: -------------------------------------------------------------------------------- 1 | /* enable tailwindcss */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | -------------------------------------------------------------------------------- /packages/vue-element-plus/src/upstream.ts: -------------------------------------------------------------------------------- 1 | export * from '@cphayim-enc/base' 2 | export { EncCSSVariables } from '@cphayim-enc/style' 3 | 4 | // exclude that have been extended in the current package 5 | export { 6 | // hooks 7 | useEventLock, 8 | type Emitter, 9 | useMitten, 10 | useEmitter, 11 | useFormData, 12 | useFormItems, 13 | usePagination, 14 | useCountEvent, 15 | 16 | // components 17 | EncKeepAliveRouterView, 18 | EncTransition, 19 | } from '@cphayim-enc/vue' 20 | -------------------------------------------------------------------------------- /packages/vue-element-plus/tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | const base = require('../../scripts/tailwind.base.config.cjs') 2 | 3 | module.exports = { 4 | ...base, 5 | } 6 | -------------------------------------------------------------------------------- /packages/vue-element-plus/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "include": ["src"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/vue-element-plus/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { UserConfigExport, defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import vueJsx from '@vitejs/plugin-vue-jsx' 4 | import defineOptions from 'unplugin-vue-define-options/vite' 5 | 6 | import { createBuild, genStylePlugin } from '../../scripts/vite.base.config' 7 | 8 | export default defineConfig(({ mode }) => { 9 | const config: UserConfigExport = { 10 | build: createBuild({ 11 | mode, 12 | root: __dirname, 13 | external: [ 14 | 'vue', 15 | 'vue-router', 16 | '@vueuse/core', 17 | 'element-plus', 18 | '@element-plus/icons-vue', 19 | /^element-plus\/es/, 20 | ], 21 | }), 22 | plugins: [ 23 | vue(), 24 | vueJsx(), 25 | defineOptions(), 26 | genStylePlugin({ 27 | preImports: [ 28 | 'element-plus/es/components/message/style/css', 29 | 'element-plus/es/components/notification/style/css', 30 | '@cphayim-enc/vue/style', 31 | ], 32 | }), 33 | ], 34 | } 35 | 36 | // after the build, use vue-tsc to generate the type declaration file 37 | 38 | return config 39 | }) 40 | -------------------------------------------------------------------------------- /packages/vue-vant/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@ombro/eslint-config-vue3-typescript'], 4 | rules: { 5 | 'prefer-const': 'off', 6 | 'vue/multi-word-component-names': 'off', 7 | '@typescript-eslint/no-empty-interface': 'off', 8 | '@typescript-eslint/no-non-null-assertion': 'off', 9 | '@typescript-eslint/no-explicit-any': 'off', 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /packages/vue-vant/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cphayim-enc/vue-vant", 3 | "version": "0.3.3", 4 | "description": "", 5 | "keywords": [], 6 | "author": "Cphayim ", 7 | "homepage": "https://github.com/Cphayim/enc#readme", 8 | "license": "ISC", 9 | "type": "module", 10 | "main": "./dist/index.js", 11 | "types": "./dist/index.d.ts", 12 | "exports": { 13 | ".": { 14 | "import": "./dist/index.js", 15 | "require": "./dist/index.cjs", 16 | "types": "./dist/index.d.ts" 17 | }, 18 | "./style": { 19 | "import": "./dist/style.js", 20 | "require": "./dist/style.cjs", 21 | "types": "./dist/style.d.ts" 22 | } 23 | }, 24 | "files": [ 25 | "dist" 26 | ], 27 | "scripts": { 28 | "dev": "vite build --mode=development --watch", 29 | "build": "vite build && vue-tsc --declaration --emitDeclarationOnly -p tsconfig.build.json", 30 | "lint": "eslint src --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix", 31 | "test": "echo \"Error: no test specified\" && exit 1" 32 | }, 33 | "dependencies": { 34 | "@cphayim-enc/base": "workspace:*", 35 | "@cphayim-enc/shared": "workspace:*", 36 | "@cphayim-enc/style": "workspace:*", 37 | "@cphayim-enc/vue": "workspace:*" 38 | }, 39 | "devDependencies": { 40 | "@vueuse/core": "^10.7.0", 41 | "dayjs": "^1.11.10", 42 | "vant": "^4.8.0", 43 | "vue": "^3.3.11", 44 | "vue-router": "^4.2.5" 45 | }, 46 | "peerDependencies": { 47 | "@vueuse/core": ">=8.0.0", 48 | "vant": "^4.0.0", 49 | "vue": "^3.2.25", 50 | "vue-router": "^4.0.0" 51 | }, 52 | "peerDependenciesMeta": {}, 53 | "publishConfig": { 54 | "access": "public", 55 | "registry": "https://registry.npmjs.org/" 56 | }, 57 | "repository": { 58 | "type": "git", 59 | "url": "git+https://github.com/Cphayim/enc.git", 60 | "directory": "packages/vue-vant" 61 | }, 62 | "bugs": { 63 | "url": "https://github.com/Cphayim/enc/issues" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /packages/vue-vant/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const { baseWithTailwind } = require('../../scripts/postcss.base.config.cjs') 2 | 3 | module.exports = { 4 | ...baseWithTailwind, 5 | } 6 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/form/cascader/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import CascaderFormItem from './CascaderFormItem.vue' 3 | 4 | export const EncCascaderFormItem = withInstall(CascaderFormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/form/checkbox/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import CheckboxFormItem from './CheckboxFormItem.vue' 3 | 4 | export const EncCheckboxFormItem = withInstall(CheckboxFormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/form/date/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import DateFormItem from './DateFormItem.vue' 3 | 4 | export const EncDateFormItem = withInstall(DateFormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/form/form-item/FormItem.vue: -------------------------------------------------------------------------------- 1 | 49 | 50 | 58 | 59 | 63 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/form/form-item/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import FormItem from './FormItem.vue' 3 | 4 | export const EncFormItem = withInstall(FormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/form/form/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import Form from './Form.vue' 3 | 4 | export const EncForm = withInstall(Form) 5 | export type EncFormInstanceType = InstanceType 6 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/form/form/provide.ts: -------------------------------------------------------------------------------- 1 | export type FormInternalConfig = { 2 | /** 3 | * 左侧标题宽度 4 | * @default '6.2em' 5 | */ 6 | labelWidth?: number | string 7 | /** 8 | * 标签的位置 9 | * @default 'left' 10 | */ 11 | labelPosition?: 'left' | 'right' | 'top' 12 | } 13 | 14 | export const DEFAULT_FORM_INTERNAL_CONFIG: FormInternalConfig = { 15 | labelWidth: '6.2em', 16 | labelPosition: 'left', 17 | } 18 | 19 | export const FORM_INTERNAL_CONFIG_KEY = Symbol('FORM_INTERNAL_CONFIG_KEY') 20 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/form/index.ts: -------------------------------------------------------------------------------- 1 | export * from './cascader' 2 | export * from './checkbox' 3 | export * from './date' 4 | export * from './form' 5 | export * from './form-item' 6 | export * from './input' 7 | export * from './radio' 8 | export * from './rate' 9 | export * from './select' 10 | export * from './switch' 11 | export * from './time' 12 | export * from './upload' 13 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/form/input/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import InputFormItem from './InputFormItem.vue' 3 | 4 | export const EncInputFormItem = withInstall(InputFormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/form/popup/PopupFormItem.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/form/popup/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import PopupFormItem from './PopupFormItem.vue' 3 | 4 | export const EncPopupFormItem = withInstall(PopupFormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/form/radio/RadioFormItem.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 54 | 55 | 60 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/form/radio/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import RadioFormItem from './RadioFormItem.vue' 3 | 4 | export const EncRadioFormItem = withInstall(RadioFormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/form/rate/RateFormItem.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 52 | 53 | 65 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/form/rate/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import RateFormItem from './RateFormItem.vue' 3 | 4 | export const EncRateFormItem = withInstall(RateFormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/form/select/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import SelectFormItem from './SelectFormItem.vue' 3 | 4 | export const EncSelectFormItem = withInstall(SelectFormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/form/switch/SwitchFormItem.vue: -------------------------------------------------------------------------------- 1 | 27 | 28 | 41 | 42 | 47 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/form/switch/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import SwitchFormItem from './SwitchFormItem.vue' 3 | 4 | export const EncSwitchFormItem = withInstall(SwitchFormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/form/time/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import TimeFormItem from './TimeFormItem.vue' 3 | 4 | export const EncTimeFormItem = withInstall(TimeFormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/form/upload/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import UploadFormItem from './UploadFormItem.vue' 3 | 4 | export const EncUploadFormItem = withInstall(UploadFormItem) 5 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './form' 2 | export * from './layout' 3 | export * from './router-transition' 4 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/layout/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import Layout from './Layout.vue' 3 | 4 | export type TabBarOption = { 5 | title: string 6 | icon: string 7 | routerPath: string 8 | routerName?: string 9 | } 10 | 11 | export type EncLayoutInstanceType = InstanceType 12 | export const EncLayout = withInstall(Layout) 13 | -------------------------------------------------------------------------------- /packages/vue-vant/src/components/router-transition/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '@cphayim-enc/vue' 2 | import RouterTransition from './RouterTransition.vue' 3 | 4 | export const EncRouterTransition = withInstall(RouterTransition) 5 | -------------------------------------------------------------------------------- /packages/vue-vant/src/hooks/__tests__/use-confirm.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest' 2 | 3 | const showConfirmDialogSpy = vi.fn() 4 | 5 | vi.mock('vant', async () => { 6 | return { 7 | showConfirmDialog: showConfirmDialogSpy, 8 | } 9 | }) 10 | 11 | const { useConfirm } = await import('../use-confirm') 12 | 13 | describe('useLoading', async () => { 14 | it('should use showConfirmDialog', async () => { 15 | const onConfirm = vi.fn() 16 | const onCancel = vi.fn() 17 | const wrappedFn = useConfirm({ 18 | message: 'test message', 19 | title: 'test title', 20 | confirmText: 'test confirm text', 21 | cancelText: 'test cancel text', 22 | onConfirm, 23 | onCancel, 24 | }) 25 | await wrappedFn('123') 26 | expect(showConfirmDialogSpy).toBeCalledTimes(1) 27 | expect(showConfirmDialogSpy.mock.lastCall![0]).toMatchObject({ 28 | title: 'test title', 29 | message: 'test message', 30 | confirmButtonText: 'test confirm text', 31 | cancelButtonText: 'test cancel text', 32 | showConfirmButton: true, 33 | showCancelButton: true, 34 | }) 35 | expect(onConfirm).toBeCalledTimes(1) 36 | expect(onConfirm).toBeCalledWith('123') 37 | expect(onCancel).toBeCalledTimes(0) 38 | 39 | showConfirmDialogSpy.mockImplementationOnce(() => Promise.reject()) 40 | await wrappedFn('456') 41 | expect(showConfirmDialogSpy).toBeCalledTimes(2) 42 | expect(showConfirmDialogSpy.mock.lastCall![0]).toMatchObject({ 43 | title: 'test title', 44 | message: 'test message', 45 | confirmButtonText: 'test confirm text', 46 | cancelButtonText: 'test cancel text', 47 | showConfirmButton: true, 48 | showCancelButton: true, 49 | }) 50 | expect(onConfirm).toBeCalledTimes(1) 51 | expect(onCancel).toBeCalledTimes(1) 52 | expect(onCancel).toBeCalledWith('456') 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /packages/vue-vant/src/hooks/__tests__/use-form.spec.tsx: -------------------------------------------------------------------------------- 1 | import { mount } from '@vue/test-utils' 2 | import { describe, expect, it } from 'vitest' 3 | 4 | import { EncForm } from '../../components' 5 | import { useForm } from '../use-form' 6 | 7 | describe('useForm', () => { 8 | it('should be exists extra properties', () => { 9 | const { formRef, setFormRef } = useForm({}, []) 10 | expect(formRef).toBeDefined() 11 | expect(setFormRef).toBeDefined() 12 | }) 13 | 14 | it('should be set formRef', () => { 15 | const { formData, formItems, formRef, setFormRef } = useForm({}, []) 16 | expect(formRef.value).toBeUndefined() 17 | 18 | mount(() => ) 19 | expect(formRef.value).toBeDefined() 20 | 21 | setFormRef(undefined) 22 | expect(formRef.value).toBeUndefined() 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /packages/vue-vant/src/hooks/__tests__/use-loading.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest' 2 | import { createConsoleSpy } from '@cphayim-enc/test-utils' 3 | 4 | const closeFn = vi.fn() 5 | const showToastSpy = vi.fn((_: any) => ({ close: closeFn })) 6 | 7 | vi.mock('vant', async () => { 8 | const module = await vi.importActual('vant') 9 | 10 | return { 11 | ...module, 12 | showToast: showToastSpy, 13 | showLoadingToast: showToastSpy, 14 | } 15 | }) 16 | 17 | const { useLoading } = await import('../use-loading') 18 | 19 | describe('useLoading', async () => { 20 | it('should use showToast on success', async () => { 21 | createConsoleSpy() 22 | 23 | const fn = vi.fn() 24 | const wrappedFn = useLoading(fn, { 25 | message: 'test loading', 26 | successMessage: 'test success', 27 | errorMessage: 'test error', 28 | }) 29 | await wrappedFn() 30 | expect(fn).toHaveBeenCalled() 31 | expect(showToastSpy).toBeCalledTimes(2) // loading + success 32 | expect(showToastSpy.mock.calls[0][0]).toMatchObject({ message: 'test loading' }) 33 | expect(closeFn).toHaveBeenCalled() 34 | expect(showToastSpy.mock.calls[1][0]).toMatchObject({ message: 'test success' }) 35 | }) 36 | 37 | it('should use showToast on error', async () => { 38 | createConsoleSpy() 39 | 40 | const fn = vi.fn(() => { 41 | throw new Error() 42 | }) 43 | const wrappedFn = useLoading(fn, { 44 | message: 'test loading', 45 | successMessage: 'test success', 46 | errorMessage: 'test error', 47 | }) 48 | await wrappedFn() 49 | expect(fn).toHaveBeenCalled() 50 | expect(showToastSpy).toBeCalledTimes(2) // loading + error 51 | expect(showToastSpy.mock.calls[0][0]).toMatchObject({ message: 'test loading' }) 52 | expect(closeFn).toHaveBeenCalled() 53 | expect(showToastSpy.mock.calls[1][0]).toMatchObject({ message: 'test error' }) 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /packages/vue-vant/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './use-loading' 2 | export * from './use-confirm' 3 | 4 | export * from './use-form' 5 | -------------------------------------------------------------------------------- /packages/vue-vant/src/hooks/use-confirm.ts: -------------------------------------------------------------------------------- 1 | import { showConfirmDialog } from 'vant' 2 | import 'vant/es/dialog/style/index' 3 | import { useConfirm as _useConfirm, type UseConfirmOptions } from '@cphayim-enc/vue' 4 | 5 | export type { UseConfirmOptions } 6 | 7 | export function useConfirm( 8 | options?: UseConfirmOptions, 9 | ): (...args: T) => Promise { 10 | return _useConfirm({ 11 | confirmor: async (args) => { 12 | try { 13 | await showConfirmDialog({ 14 | title: args.title, 15 | message: args.message, 16 | confirmButtonText: args.confirmText, 17 | cancelButtonText: args.cancelText, 18 | showConfirmButton: true, 19 | showCancelButton: true, 20 | }) 21 | return true 22 | } catch (error) { 23 | return false 24 | } 25 | }, 26 | ...options, 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /packages/vue-vant/src/hooks/use-form.ts: -------------------------------------------------------------------------------- 1 | import { type Ref, ref } from 'vue' 2 | 3 | import type { FormItemUnion } from '@cphayim-enc/base' 4 | import { useForm as _useForm, type UseFormOptions } from '@cphayim-enc/vue' 5 | import type { EncFormInstanceType } from '../components' 6 | 7 | type EncFormInstance = EncFormInstanceType | undefined 8 | 9 | /** 10 | * 对等表单 useForm hooks 11 | */ 12 | export function useForm, F = Extract>( 13 | initData: T, 14 | items: FormItemUnion[] | Ref[]>, 15 | options?: UseFormOptions, 16 | ) { 17 | const formRef = ref() 18 | const setFormRef = (instance: EncFormInstance) => { 19 | formRef.value = instance 20 | } 21 | 22 | return { 23 | ..._useForm(initData, items, options), 24 | formRef, 25 | setFormRef, 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/vue-vant/src/hooks/use-loading.ts: -------------------------------------------------------------------------------- 1 | import { showLoadingToast, showToast } from 'vant' 2 | import 'vant/es/toast/style/index' 3 | 4 | import { useLoading as _useLoading } from '@cphayim-enc/vue' 5 | import type { UseLoadingOptions } from '@cphayim-enc/vue' 6 | 7 | const DEFAULT_DURATION = 3000 8 | 9 | export function useLoading( 10 | fn: (...args: T) => void | Promise, 11 | options?: UseLoadingOptions, 12 | ): (...args: T) => Promise { 13 | return _useLoading(fn, { 14 | onLoading: (message) => { 15 | return showLoadingToast({ message, duration: 0 }) 16 | }, 17 | onClearLoading: (flag: ReturnType) => { 18 | flag.close() 19 | }, 20 | onSuccess: (message) => { 21 | showToast({ message, duration: options?.duration ?? DEFAULT_DURATION }) 22 | }, 23 | onError: (message) => { 24 | showToast({ message, duration: options?.duration ?? DEFAULT_DURATION }) 25 | }, 26 | ...options, 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /packages/vue-vant/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { App, Plugin } from 'vue' 2 | 3 | import { type CreateEncOptions, usingSFCWithInstall } from '@cphayim-enc/vue' 4 | 5 | import './style.css' 6 | import * as componentMap from './components' 7 | 8 | export const createEncVant = ({ skipEncInstall = false }: CreateEncOptions = {}): Plugin => { 9 | return { 10 | install(app: App) { 11 | if (!skipEncInstall) { 12 | usingSFCWithInstall(app, componentMap) 13 | } 14 | }, 15 | } 16 | } 17 | 18 | export default createEncVant() 19 | 20 | export * from './upstream' 21 | export * from './hooks' 22 | export * from './components' 23 | -------------------------------------------------------------------------------- /packages/vue-vant/src/style.css: -------------------------------------------------------------------------------- 1 | /* enable tailwindcss */ 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | 6 | :root { 7 | --enc-vant-radio-size: 16px; 8 | --enc-vant-checkbox-size: 16px; 9 | --enc-vant-switch-size: 20px; 10 | } 11 | -------------------------------------------------------------------------------- /packages/vue-vant/src/upstream.ts: -------------------------------------------------------------------------------- 1 | export * from '@cphayim-enc/base' 2 | export { EncCSSVariables } from '@cphayim-enc/style' 3 | 4 | // exclude that have been extended in the current package 5 | export { 6 | // hooks 7 | useEventLock, 8 | type Emitter, 9 | useMitten, 10 | useEmitter, 11 | useFormData, 12 | useFormItems, 13 | usePagination, 14 | useCountEvent, 15 | 16 | // components 17 | EncKeepAliveRouterView, 18 | EncTransition, 19 | } from '@cphayim-enc/vue' 20 | -------------------------------------------------------------------------------- /packages/vue-vant/tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | const base = require('../../scripts/tailwind.base.config.cjs') 2 | 3 | module.exports = { 4 | ...base, 5 | } 6 | -------------------------------------------------------------------------------- /packages/vue-vant/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "include": ["src"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/vue-vant/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { UserConfigExport, defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import vueJsx from '@vitejs/plugin-vue-jsx' 4 | import defineOptions from 'unplugin-vue-define-options/vite' 5 | 6 | import { createBuild, genStylePlugin } from '../../scripts/vite.base.config' 7 | 8 | export default defineConfig(({ mode }) => { 9 | const config: UserConfigExport = { 10 | build: createBuild({ 11 | mode, 12 | root: __dirname, 13 | external: ['vue', 'vue-router', '@vueuse/core', 'vant', /^vant\/es/], 14 | }), 15 | plugins: [ 16 | vue(), 17 | vueJsx(), 18 | defineOptions(), 19 | genStylePlugin({ preImports: ['vant/es/toast/style/index', '@cphayim-enc/vue/style'] }), 20 | ], 21 | } 22 | 23 | return config 24 | }) 25 | -------------------------------------------------------------------------------- /packages/vue/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@ombro/eslint-config-vue3-typescript'], 4 | rules: { 5 | 'prefer-const': 'off', 6 | 'vue/multi-word-component-names': 'off', 7 | '@typescript-eslint/no-empty-interface': 'off', 8 | '@typescript-eslint/no-non-null-assertion': 'off', 9 | '@typescript-eslint/no-explicit-any': 'off', 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /packages/vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@cphayim-enc/vue", 3 | "version": "0.3.3", 4 | "description": "", 5 | "keywords": [], 6 | "author": "Cphayim ", 7 | "homepage": "https://github.com/Cphayim/enc#readme", 8 | "license": "ISC", 9 | "type": "module", 10 | "main": "./dist/index.js", 11 | "types": "./dist/index.d.ts", 12 | "exports": { 13 | ".": { 14 | "import": "./dist/index.js", 15 | "require": "./dist/index.cjs", 16 | "types": "./dist/index.d.ts" 17 | }, 18 | "./style": { 19 | "import": "./dist/style.js", 20 | "require": "./dist/style.cjs", 21 | "types": "./dist/style.d.ts" 22 | } 23 | }, 24 | "files": [ 25 | "dist" 26 | ], 27 | "scripts": { 28 | "dev": "vite build --mode=development --watch", 29 | "build": "vite build && vue-tsc --declaration --emitDeclarationOnly -p tsconfig.build.json", 30 | "lint": "eslint src --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix", 31 | "test": "echo \"Error: no test specified\" && exit 1" 32 | }, 33 | "dependencies": { 34 | "@cphayim-enc/base": "workspace:*", 35 | "@cphayim-enc/shared": "workspace:*", 36 | "@cphayim-enc/style": "workspace:*", 37 | "@ombro/mitten": "^0.1.0" 38 | }, 39 | "devDependencies": { 40 | "@vueuse/core": "^10.7.0", 41 | "vue": "^3.3.11" 42 | }, 43 | "peerDependencies": { 44 | "@vueuse/core": ">=8.0.0", 45 | "vue": "^3.2.25" 46 | }, 47 | "peerDependenciesMeta": {}, 48 | "publishConfig": { 49 | "access": "public", 50 | "registry": "https://registry.npmjs.org/" 51 | }, 52 | "repository": { 53 | "type": "git", 54 | "url": "git+https://github.com/Cphayim/enc.git", 55 | "directory": "packages/vue" 56 | }, 57 | "bugs": { 58 | "url": "https://github.com/Cphayim/enc/issues" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/vue/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | const { base } = require('../../scripts/postcss.base.config.cjs') 2 | 3 | module.exports = { 4 | ...base, 5 | } 6 | -------------------------------------------------------------------------------- /packages/vue/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './keep-alive-router-view' 2 | export * from './transition' 3 | -------------------------------------------------------------------------------- /packages/vue/src/components/keep-alive-router-view/KeepAliveRouterView.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 31 | -------------------------------------------------------------------------------- /packages/vue/src/components/keep-alive-router-view/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '../../install' 2 | import KeepAliveRouterView from './KeepAliveRouterView.vue' 3 | 4 | export const EncKeepAliveRouterView = withInstall(KeepAliveRouterView) 5 | -------------------------------------------------------------------------------- /packages/vue/src/components/transition/Transition.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/vue/src/components/transition/hooks.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue' 2 | 3 | type DurationProps = { 4 | duration?: number | string 5 | } 6 | export function useDuration(props: DurationProps) { 7 | return computed(() => 8 | typeof props.duration === 'number' ? `${props.duration}ms` : props.duration, 9 | ) 10 | } 11 | 12 | export type TimingFunction = 'ease' | 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out' 13 | type TimingFunctionProps = { 14 | timingFunction?: TimingFunction 15 | } 16 | export function useTimingFunction(props: TimingFunctionProps) { 17 | return computed(() => props.timingFunction) 18 | } 19 | -------------------------------------------------------------------------------- /packages/vue/src/components/transition/index.ts: -------------------------------------------------------------------------------- 1 | import { withInstall } from '../../install' 2 | import Transition from './Transition.vue' 3 | import Fade from './Fade.vue' 4 | import Slide from './Slide.vue' 5 | import Zoom from './Zoom.vue' 6 | 7 | type ComposeTransition = typeof Transition & { 8 | Fade: typeof Fade 9 | Slide: typeof Slide 10 | Zoom: typeof Zoom 11 | } 12 | 13 | Transition.Fade = Fade 14 | Transition.Slide = Slide 15 | Transition.Zoom = Zoom 16 | 17 | export const EncTransition = withInstall(Transition as ComposeTransition) 18 | -------------------------------------------------------------------------------- /packages/vue/src/hooks/__tests__/use-emitter.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest' 2 | 3 | import { useEmitter } from '../use-emitter' 4 | 5 | describe('useEmitter', () => { 6 | it('should be able to subscribe to and publish events', () => { 7 | const emitter = useEmitter<{ foo: undefined; bar: string }>() 8 | const handleFoo = vi.fn() 9 | const handleBar = vi.fn().mockImplementation((_: string) => void 0) 10 | 11 | emitter.on('foo', handleFoo) 12 | emitter.on('bar', handleBar) 13 | 14 | emitter.emit('foo') 15 | expect(handleFoo).toHaveBeenCalledTimes(1) 16 | emitter.emit('bar', 'baz') 17 | expect(handleBar).toHaveBeenCalledTimes(1) 18 | expect(handleBar).toHaveBeenCalledWith('baz') 19 | 20 | emitter.emit('foo') 21 | expect(handleFoo).toHaveBeenCalledTimes(2) 22 | emitter.emit('bar', 'baz') 23 | expect(handleBar).toHaveBeenCalledTimes(2) 24 | expect(handleBar).toHaveBeenCalledWith('baz') 25 | }) 26 | 27 | it('should be able to unsubscribe from events', () => { 28 | const emitter = useEmitter<{ foo: undefined }>() 29 | const handleFoo = vi.fn() 30 | 31 | emitter.on('foo', handleFoo) 32 | emitter.off('foo', handleFoo) 33 | 34 | emitter.emit('foo') 35 | expect(handleFoo).not.toHaveBeenCalled() 36 | }) 37 | 38 | it('should be able to add multiple processing callbacks', () => { 39 | const emitter = useEmitter<{ foo: undefined }>() 40 | const handleFoo1 = vi.fn() 41 | const handleFoo2 = vi.fn() 42 | 43 | emitter.on('foo', handleFoo1) 44 | emitter.on('foo', handleFoo2) 45 | 46 | emitter.emit('foo') 47 | expect(handleFoo1).toHaveBeenCalledTimes(1) 48 | expect(handleFoo2).toHaveBeenCalledTimes(1) 49 | }) 50 | }) 51 | -------------------------------------------------------------------------------- /packages/vue/src/hooks/__tests__/use-event-lock.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it, vi } from 'vitest' 2 | 3 | import { sleep } from '@cphayim-enc/shared' 4 | import { useEventLock } from '../use-event-lock' 5 | 6 | describe('useEventLock', () => { 7 | it('should not repeat called with wrapped function', async () => { 8 | const fn = vi.fn().mockImplementation(async () => { 9 | await sleep(1) 10 | }) 11 | const wrappedFn = useEventLock(fn) 12 | wrappedFn() 13 | wrappedFn() 14 | wrappedFn() 15 | expect(fn).toHaveBeenCalledTimes(1) 16 | }) 17 | 18 | it('should be unlocked after the call', async () => { 19 | const fn = vi.fn().mockImplementation(async () => { 20 | await sleep(5) 21 | }) 22 | const wrappedFn = useEventLock(fn) 23 | await wrappedFn() 24 | expect(fn).toHaveBeenCalledTimes(1) 25 | wrappedFn() 26 | expect(fn).toHaveBeenCalledTimes(2) 27 | }) 28 | 29 | it('should be unlocked when an exception is thrown', async () => { 30 | const fn = vi.fn().mockImplementation(() => { 31 | throw new Error('test') 32 | }) 33 | const wrappedFn = useEventLock(fn) 34 | await expect(wrappedFn).rejects.toThrow('test') 35 | expect(fn).toHaveBeenCalledTimes(1) 36 | 37 | expect(wrappedFn).rejects.toThrow('test') 38 | expect(fn).toHaveBeenCalledTimes(2) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /packages/vue/src/hooks/__tests__/use-form-data.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { isRef } from 'vue' 3 | 4 | import { useFormData } from '../use-form-data' 5 | 6 | describe('useFormData', () => { 7 | it('should return a ref formData', () => { 8 | const { formData } = useFormData({ name: 'enc', age: 18 }) 9 | expect(isRef(formData)).toBe(true) 10 | expect(formData.value).toEqual({ name: 'enc', age: 18 }) 11 | }) 12 | 13 | it('should reset formData', () => { 14 | const { formData, resetData } = useFormData({ name: 'enc', age: 18 }) 15 | formData.value.name = 'test' 16 | formData.value.age = 20 17 | expect(formData.value).toEqual({ name: 'test', age: 20 }) 18 | resetData() 19 | expect(formData.value).toEqual({ name: 'enc', age: 18 }) 20 | }) 21 | }) 22 | -------------------------------------------------------------------------------- /packages/vue/src/hooks/__tests__/use-form.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { isRef } from 'vue' 3 | 4 | import type { FormItemUnion } from '@cphayim-enc/base' 5 | 6 | import { useForm } from '../use-form' 7 | 8 | describe('useForm', () => { 9 | const items: FormItemUnion[] = [ 10 | { 11 | name: 'name', 12 | label: '姓名', 13 | type: 'input', 14 | rules: [{ required: true, message: '请输入姓名' }], 15 | }, 16 | { 17 | name: 'age', 18 | label: '年龄', 19 | type: 'input', 20 | }, 21 | ] 22 | 23 | it('should be combined useFormData and useFormItems', () => { 24 | const { formData, resetData, formItems, getItem, updateItem } = useForm( 25 | { name: 'enc', age: 18 }, 26 | items, 27 | ) 28 | expect(isRef(formData)).toBe(true) 29 | expect(resetData).toBeDefined() 30 | expect(isRef(formItems)).toBe(true) 31 | expect(getItem).toBeDefined() 32 | expect(updateItem).toBeDefined() 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /packages/vue/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export * from './use-event-lock' 2 | export * from './use-loading' 3 | export * from './use-confirm' 4 | export * from './use-emitter' 5 | export * from './use-pagination' 6 | export { 7 | useCountEvent, 8 | type CountEventHandler, 9 | type CountEventHandlerMap, 10 | type UseCountEventOptions, 11 | } from './use-count-event' 12 | 13 | export * from './use-form-data' 14 | export * from './use-form-items' 15 | export * from './use-form' 16 | -------------------------------------------------------------------------------- /packages/vue/src/hooks/use-confirm.ts: -------------------------------------------------------------------------------- 1 | export type ConfirmorArgs = { 2 | /** 3 | * 确认框标题 4 | * @default '提示' 5 | */ 6 | title?: string 7 | /** 8 | * 确认框内容 9 | * @default '确认执行此操作吗?' 10 | */ 11 | message?: string 12 | /** 13 | * 确认按钮文本 14 | * @default '确定' 15 | */ 16 | confirmText?: string 17 | /** 18 | * 取消按钮文本 19 | * @default '取消' 20 | */ 21 | cancelText?: string 22 | } 23 | 24 | export type Confirmor = (args: ConfirmorArgs) => boolean | Promise 25 | 26 | export type UseConfirmOptions = ConfirmorArgs & { 27 | /** 28 | * 确认时回调 29 | */ 30 | onConfirm?: (...args: T) => void | Promise 31 | /** 32 | * 取消时回调 33 | */ 34 | onCancel?: (...args: T) => void | Promise 35 | /** 36 | * 确认器 37 | * @default promiseConfirmor 38 | */ 39 | confirmor?: Confirmor 40 | } 41 | 42 | const DEFAULT_OPTIONS: UseConfirmOptions = { 43 | title: '提示', 44 | message: '确认执行此操作吗?', 45 | confirmText: '确定', 46 | cancelText: '取消', 47 | confirmor: (args) => { 48 | const { title, message } = args 49 | return new Promise((resolve) => { 50 | // 浏览器自带 confirm 51 | const confirm = globalThis?.confirm(`${title}\n${message}`) 52 | resolve(!!confirm) 53 | }) 54 | }, 55 | } 56 | 57 | export function useConfirm( 58 | options?: UseConfirmOptions, 59 | ): (...args: T) => Promise { 60 | const { onConfirm, onCancel, ...confirmorArgs } = { 61 | ...DEFAULT_OPTIONS, 62 | ...options, 63 | } 64 | return async (...args) => { 65 | const { confirmor } = confirmorArgs 66 | const result = await confirmor?.(confirmorArgs) 67 | if (result) { 68 | await onConfirm?.(...args) 69 | } else { 70 | await onCancel?.(...args) 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /packages/vue/src/hooks/use-emitter.ts: -------------------------------------------------------------------------------- 1 | import mitten, { type Emitter, type EventType } from '@ombro/mitten' 2 | 3 | export type { Emitter } 4 | 5 | export function useMitten>() { 6 | return mitten() 7 | } 8 | 9 | export function useEmitter>() { 10 | return useMitten() 11 | } 12 | -------------------------------------------------------------------------------- /packages/vue/src/hooks/use-event-lock.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue' 2 | 3 | /** 4 | * 事件锁,防止函数重复调用 5 | * @param fn 6 | * @returns 7 | */ 8 | export function useEventLock( 9 | fn: (...args: T) => void | Promise, 10 | ): (...args: T) => Promise { 11 | const lock = ref(false) 12 | return async (...args) => { 13 | if (lock.value) return 14 | lock.value = true 15 | try { 16 | await fn(...args) 17 | } finally { 18 | lock.value = false 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/vue/src/hooks/use-form-data.ts: -------------------------------------------------------------------------------- 1 | import { type Ref, ref } from 'vue' 2 | 3 | import { deepClone } from '@cphayim-enc/shared' 4 | 5 | export function useFormData>(initData: T) { 6 | const formData = ref(getInitialData(initData)) as Ref 7 | const resetData = () => { 8 | formData.value = getInitialData(initData) 9 | } 10 | 11 | return { 12 | formData, 13 | resetData, 14 | } 15 | } 16 | 17 | function getInitialData>(originalData: T) { 18 | return deepClone(originalData) 19 | } 20 | -------------------------------------------------------------------------------- /packages/vue/src/hooks/use-form-items.ts: -------------------------------------------------------------------------------- 1 | import { computed, isRef, ref, type Ref, watchEffect } from 'vue' 2 | 3 | import type { 4 | FormItemUnion, 5 | PartialFormItemIntersectionWithoutNameAndType, 6 | } from '@cphayim-enc/base' 7 | import { createErrorMessage } from '@cphayim-enc/shared' 8 | 9 | export function useFormItems( 10 | items: FormItemUnion[] | Ref[]>, 11 | commonItem?: PartialFormItemIntersectionWithoutNameAndType, 12 | ) { 13 | const formItems = ref([]) as Ref[]> 14 | 15 | // 如果 items 是一个 Ref,则当外部 items 更新时,应该重新赋值 formItems 16 | watchEffect(() => { 17 | formItems.value = mergeItems(isRef(items) ? items.value : items, commonItem) 18 | }) 19 | 20 | const formItemsMap = computed(() => getCacheMap(formItems.value)) 21 | 22 | const getItem = (name: F) => formItemsMap.value.get(name) 23 | 24 | const updateItem = (name: F, updateItem: PartialFormItemIntersectionWithoutNameAndType) => { 25 | const item = getItem(name) 26 | if (item) { 27 | Object.assign(item, updateItem) 28 | } else { 29 | throw new Error(createErrorMessage(`updateItem not found name: ${name}`)) 30 | } 31 | } 32 | 33 | return { 34 | formItems, 35 | getItem, 36 | updateItem, 37 | } 38 | } 39 | 40 | function mergeItems( 41 | items: FormItemUnion[] = [], 42 | commonItem: PartialFormItemIntersectionWithoutNameAndType = {}, 43 | ): FormItemUnion[] { 44 | return items.map((item) => ({ ...commonItem, ...item })) 45 | } 46 | 47 | function getCacheMap(items: FormItemUnion[]) { 48 | const map = new Map>() 49 | items.forEach((item) => map.set(item.name, item)) 50 | return map 51 | } 52 | -------------------------------------------------------------------------------- /packages/vue/src/hooks/use-form.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue' 2 | 3 | import type { 4 | FormItemUnion, 5 | PartialFormItemIntersectionWithoutNameAndType, 6 | } from '@cphayim-enc/base' 7 | import { useFormData } from './use-form-data' 8 | import { useFormItems } from './use-form-items' 9 | 10 | export type UseFormOptions = { 11 | /** 12 | * 默认表单配置项,将和 items 的每一项合并 13 | */ 14 | commonItem?: PartialFormItemIntersectionWithoutNameAndType 15 | /** 16 | * 兼容字段,同 commonItem 17 | * @deprecated 18 | */ 19 | defaultProps?: PartialFormItemIntersectionWithoutNameAndType 20 | } 21 | 22 | /** 23 | * 对等表单通用 useForm hooks 24 | * 25 | * UI package 可以进行扩展 26 | */ 27 | export function useForm, F = Extract>( 28 | initData: T, 29 | items: FormItemUnion[] | Ref[]>, 30 | options: UseFormOptions = {}, 31 | ) { 32 | return { 33 | ...useFormData(initData), 34 | ...useFormItems(items, options.commonItem ?? options.defaultProps), 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/vue/src/index.ts: -------------------------------------------------------------------------------- 1 | import './style.css' 2 | 3 | export * from '@cphayim-enc/base' 4 | export { EncCSSVariables } from '@cphayim-enc/style' 5 | 6 | export * from './install' 7 | export * from './hooks' 8 | export * from './components' 9 | 10 | export type CreateEncOptions = { 11 | /** 12 | * 跳过依赖组件的安装 13 | */ 14 | skipDepsInstall?: boolean 15 | /** 16 | * 跳过 enc 组件的安装 17 | */ 18 | skipEncInstall?: boolean 19 | } 20 | -------------------------------------------------------------------------------- /packages/vue/src/install.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import type { App, Plugin } from 'vue' 3 | 4 | export type SFCWithInstall = T & Plugin 5 | 6 | /** 7 | * 为组件添加 install 方法 8 | */ 9 | export const withInstall = (comp: T): SFCWithInstall => { 10 | ;(comp as any).install = (app: App): void => { 11 | app.component((comp as any).name, comp as any) 12 | } 13 | return comp as SFCWithInstall 14 | } 15 | 16 | /** 17 | * 判断传入的组件是否是 SFCWithInstall 类型 18 | */ 19 | export function isSFCWithInstall(component: T): component is SFCWithInstall { 20 | return !!(component as any)?.install 21 | } 22 | 23 | /** 24 | * 批量安装组件 app.use(xxx) 25 | */ 26 | export function usingSFCWithInstall( 27 | app: App, 28 | components: 29 | | SFCWithInstall 30 | | SFCWithInstall[] 31 | | Record>, 32 | ) { 33 | if (isSFCWithInstall(components)) { 34 | app.use(components) 35 | } else if (Array.isArray(components)) { 36 | components.forEach((component) => isSFCWithInstall(component) && app.use(component)) 37 | } else { 38 | Object.values(components).forEach( 39 | (component) => isSFCWithInstall(component) && app.use(component), 40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/vue/src/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --enc-vue: true; 3 | } 4 | -------------------------------------------------------------------------------- /packages/vue/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.build.json", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "include": ["src"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/vue/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { UserConfigExport, defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import vueJsx from '@vitejs/plugin-vue-jsx' 4 | import defineOptions from 'unplugin-vue-define-options/vite' 5 | 6 | import { createBuild, genStylePlugin } from '../../scripts/vite.base.config' 7 | 8 | export default defineConfig(({ mode }) => { 9 | const config: UserConfigExport = { 10 | build: createBuild({ mode, root: __dirname, external: ['vue', '@vueuse/core'] }), 11 | plugins: [ 12 | vue(), 13 | vueJsx(), 14 | defineOptions(), 15 | genStylePlugin({ preImports: ['@cphayim-enc/style/style'] }), 16 | ], 17 | } 18 | 19 | return config 20 | }) 21 | -------------------------------------------------------------------------------- /playgrounds/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cphayim/enc/9170ebe79fd8caf7512b0e54b507862864fedf13/playgrounds/.gitkeep -------------------------------------------------------------------------------- /playgrounds/vue/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ['@ombro/eslint-config-vue3-typescript'], 4 | rules: { 5 | 'prefer-const': 'off', 6 | 'vue/multi-word-component-names': 'off', 7 | '@typescript-eslint/no-empty-interface': 'off', 8 | '@typescript-eslint/no-non-null-assertion': 'off', 9 | '@typescript-eslint/no-explicit-any': 'off', 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /playgrounds/vue/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Enc vue playground 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /playgrounds/vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@playground/vue", 3 | "private": true, 4 | "description": "", 5 | "keywords": [], 6 | "license": "ISC", 7 | "type": "module", 8 | "scripts": { 9 | "dev": "vite --host", 10 | "build": "vite build", 11 | "build:debug": "vite build", 12 | "lint": "eslint src --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix ", 13 | "test": "echo \"Error: no test specified\" && exit 1" 14 | }, 15 | "dependencies": { 16 | "@cphayim-enc/base": "workspace:*", 17 | "@cphayim-enc/extension-vue-form-editor": "workspace:*", 18 | "@cphayim-enc/vue-element-plus": "workspace:*", 19 | "@cphayim-enc/vue-vant": "workspace:*", 20 | "@element-plus/icons-vue": "^2.3.1", 21 | "@vueuse/core": "^10.7.0", 22 | "element-plus": "^2.4.3", 23 | "vue": "^3.3.11", 24 | "vue-router": "^4.2.5" 25 | }, 26 | "version": null 27 | } 28 | -------------------------------------------------------------------------------- /playgrounds/vue/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | 'tailwindcss/nesting': {}, 4 | tailwindcss: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /playgrounds/vue/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /playgrounds/vue/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const FORM_ITEMS_STORE_KEY = '@enc-pg-vue/form-items' 2 | -------------------------------------------------------------------------------- /playgrounds/vue/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /playgrounds/vue/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | 3 | import { createEncElementPlus } from '@cphayim-enc/vue-element-plus' 4 | import '@cphayim-enc/vue-element-plus/style' 5 | 6 | import { createEncVant } from '@cphayim-enc/vue-vant' 7 | import '@cphayim-enc/vue-vant/style' 8 | 9 | import { createEncExtensionFormEditor } from '@cphayim-enc/extension-vue-form-editor' 10 | import '@cphayim-enc/extension-vue-form-editor/style' 11 | 12 | import App from './App.vue' 13 | import router from './router' 14 | import './style.css' 15 | 16 | createApp(App) 17 | .use(router) 18 | .use(createEncElementPlus({ skipEncInstall: true })) 19 | .use(createEncVant({ skipEncInstall: true })) 20 | .use(createEncExtensionFormEditor({ skipEncInstall: true })) 21 | .mount('#app') 22 | -------------------------------------------------------------------------------- /playgrounds/vue/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router' 2 | 3 | import routes from './routes' 4 | 5 | export default createRouter({ 6 | history: createWebHistory(import.meta.env.BASE_URL), 7 | routes, 8 | }) 9 | -------------------------------------------------------------------------------- /playgrounds/vue/src/router/routes.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw } from 'vue-router' 2 | 3 | import formRoutes from '../views/form/routes' 4 | import formEditorRoutes from '../views/form-editor/routes' 5 | 6 | export default [ 7 | { 8 | path: '/', 9 | redirect: '/form', 10 | }, 11 | ...formRoutes, 12 | ...formEditorRoutes, 13 | ] as RouteRecordRaw[] 14 | -------------------------------------------------------------------------------- /playgrounds/vue/src/style.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | margin: 0; 7 | } 8 | 9 | * { 10 | box-sizing: border-box; 11 | } 12 | -------------------------------------------------------------------------------- /playgrounds/vue/src/views/form-editor/routes.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw } from 'vue-router' 2 | 3 | export default [ 4 | { 5 | path: '/form-editor', 6 | name: 'form-editor', 7 | component: () => import('./index.vue'), 8 | }, 9 | ] as RouteRecordRaw[] 10 | -------------------------------------------------------------------------------- /playgrounds/vue/src/views/form/PreviewElementPlus.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /playgrounds/vue/src/views/form/PreviewVant.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /playgrounds/vue/src/views/form/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 |