├── .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 |

4 |
5 | 
6 | [](https://codecov.io/gh/Cphayim/enc)
7 | [](https://www.npmjs.com/package/@cphayim-enc/base)
8 | [](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 |
8 |
18 |
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 |
26 |
27 |
28 |
29 |
34 |
35 |
43 |
44 |
45 |
46 |
47 |
48 |
53 |
--------------------------------------------------------------------------------
/packages/extension-vue-form-editor/src/components/form-edit-panel/center-panel/EmptyContainer.vue:
--------------------------------------------------------------------------------
1 |
37 |
38 |
39 |
44 | {{ emptyText }}
45 |
46 |
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 |
8 |
14 |
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 |
29 |
30 |
36 |
37 |
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 |
29 |
30 |
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 |
34 |
35 |
41 |
42 |
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 |
29 |
30 |
36 |
37 |
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 |
29 |
30 |
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 |
25 |
26 |
32 |
33 |
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 |
29 |
30 |
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 |
25 |
26 |
32 |
33 |
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 |
25 |
26 |
32 |
33 |
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 |
34 |
35 |
41 |
42 |
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 |
45 |
46 |
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 |
14 |
17 |
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 |
40 |
50 |
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 |
29 |
40 |
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 |
29 |
30 |
31 |
39 | {{ (labelOrOption as RadioOptions).label ?? labelOrOption }}
40 |
41 |
42 |
43 |
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 |
41 |
50 |
51 | {{ currentText }}
52 |
53 |
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 |
30 |
40 |
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 |
29 |
36 |
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 |
52 |
60 |
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 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
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 |
44 |
58 |
61 |
62 |
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 |
29 |
30 |
31 |
32 |
37 |
48 | {{ (labelOrOption as RadioOptions).label ?? labelOrOption }}
49 |
50 |
51 |
52 |
53 |
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 |
40 |
51 |
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 |
29 |
30 |
31 |
38 |
39 |
40 |
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 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
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 |
8 |
9 |
10 |
11 |
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 |
2 |
3 |
4 |
5 |
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 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/playgrounds/vue/src/views/form/PreviewVant.vue:
--------------------------------------------------------------------------------
1 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/playgrounds/vue/src/views/form/index.vue:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
@enc/vue-element-plus
9 |
10 |
11 |
12 |
@enc/vue-vant
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/playgrounds/vue/src/views/form/routes.ts:
--------------------------------------------------------------------------------
1 | import type { RouteRecordRaw } from 'vue-router'
2 |
3 | export default [
4 | {
5 | path: '/form',
6 | name: 'form',
7 | component: () => import('./index.vue'),
8 | },
9 | {
10 | path: '/form/preview/element-plus',
11 | name: 'form-preview-element-plus',
12 | component: () => import('./PreviewElementPlus.vue'),
13 | },
14 | {
15 | path: '/form/preview/vant',
16 | name: 'form-preview-vant',
17 | component: () => import('./PreviewVant.vue'),
18 | },
19 | ] as RouteRecordRaw[]
20 |
--------------------------------------------------------------------------------
/playgrounds/vue/tailwind.config.cjs:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/no-var-requires
2 | const plugin = require('tailwindcss/plugin')
3 |
4 | module.exports = {
5 | corePlugins: {
6 | preflight: false,
7 | },
8 | content: ['./src/**/*.{vue,js,ts,jsx,tsx}'],
9 | theme: {},
10 | plugins: [],
11 | }
12 |
--------------------------------------------------------------------------------
/playgrounds/vue/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'node:path'
2 | import { defineConfig } from 'vite'
3 | import vue from '@vitejs/plugin-vue'
4 | import vueJsx from '@vitejs/plugin-vue-jsx'
5 |
6 | export default defineConfig({
7 | resolve: {
8 | alias: {
9 | '@': resolve(__dirname, 'src'),
10 | },
11 | dedupe: ['vue'],
12 | },
13 | plugins: [vue(), vueJsx()],
14 | })
15 |
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - 'packages/*'
3 | - 'playgrounds/*'
4 |
--------------------------------------------------------------------------------
/scripts/changeset.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env tsx
2 | import path from 'node:path'
3 | import { fileURLToPath } from 'node:url'
4 | import { execaCommandSync } from 'execa'
5 |
6 | const __dirname = path.dirname(fileURLToPath(import.meta.url))
7 |
8 | // add changes
9 | export async function changesetAdd() {
10 | execaCommandSync('pnpm changeset add', { stdio: 'inherit', cwd: path.join(__dirname, '..') })
11 | }
12 |
13 | // release changes to version
14 | export async function changesetVersion() {
15 | execaCommandSync('pnpm changeset version', { stdio: 'inherit', cwd: path.join(__dirname, '..') })
16 | execaCommandSync('pnpm -r exec -- rimraf CHANGELOG.md', {
17 | stdio: 'inherit',
18 | cwd: path.join(__dirname, '..'),
19 | })
20 | }
21 |
22 | if (process.argv[1] === fileURLToPath(import.meta.url)) {
23 | process.argv.includes('--version') ? changesetVersion() : changesetAdd()
24 | }
25 |
--------------------------------------------------------------------------------
/scripts/postcss.base.config.cjs:
--------------------------------------------------------------------------------
1 | const base = {
2 | plugins: {
3 | 'tailwindcss/nesting': {},
4 | },
5 | }
6 |
7 | const baseWithTailwind = {
8 | plugins: {
9 | ...base.plugins,
10 | tailwindcss: {},
11 | 'postcss-replace': {
12 | pattern: /(--tw|\*, ::before, ::after)/g,
13 | data: {
14 | '--tw': '--enc', // Prefixing
15 | '*, ::before, ::after': ':root', // So variables does not pollute every element
16 | },
17 | },
18 | },
19 | }
20 |
21 | module.exports = {
22 | base,
23 | baseWithTailwind,
24 | }
25 |
--------------------------------------------------------------------------------
/scripts/tailwind.base.config.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | prefix: 'enc-',
3 | corePlugins: {
4 | preflight: false,
5 | },
6 | content: ['./src/**/*.{vue,js,ts,jsx,tsx}'],
7 | }
8 |
--------------------------------------------------------------------------------
/scripts/unit-test.ts:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env tsx
2 | import path from 'node:path'
3 | import { fileURLToPath } from 'node:url'
4 | import { execaSync } from 'execa'
5 | import { logger } from '@ombro/logger'
6 |
7 | const __dirname = path.dirname(fileURLToPath(import.meta.url))
8 |
9 | function unitTest() {
10 | try {
11 | execaSync('vitest', ['run', ...process.argv.slice(2)], {
12 | stdio: 'inherit',
13 | cwd: path.join(__dirname, '..'),
14 | })
15 | logger.done('Passed test!')
16 | } catch (error) {
17 | logger.error('Failed test!')
18 | process.exit(error.exitCode)
19 | }
20 | }
21 |
22 | if (process.argv[1] === fileURLToPath(import.meta.url)) {
23 | unitTest()
24 | }
25 |
--------------------------------------------------------------------------------
/tsconfig.base.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "@ombro/tsconfig/tsconfig.esm.json",
3 | "compilerOptions": {
4 | "removeComments": false,
5 | "noEmitOnError": false, // vue-tsc required
6 | "resolveJsonModule": true,
7 | "types": ["unplugin-vue-define-options/macros-global"]
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/tsconfig.build.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base.json",
3 | "compilerOptions": {},
4 | "exclude": ["**/__tests__/*"]
5 | }
6 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.base.json",
3 | "compilerOptions": {
4 | "jsx": "preserve",
5 | "baseUrl": ".",
6 | "declaration": false,
7 | "paths": {
8 | "@cphayim-enc/*": ["packages/*/src"]
9 | }
10 | },
11 | "include": ["packages/*/src", "playgrounds/*/src"]
12 | }
13 |
--------------------------------------------------------------------------------
/turbo.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://turbo.build/schema.json",
3 | "pipeline": {
4 | "build": {
5 | "dependsOn": ["^build"],
6 | "outputs": ["dist/**", ".next/**"]
7 | },
8 | "lint": {
9 | "outputs": []
10 | },
11 | "dev": {
12 | "cache": false
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/vitest.config.ts:
--------------------------------------------------------------------------------
1 | import { defineConfig } from 'vitest/config'
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 | export default defineConfig({
7 | plugins: [vue(), vueJsx(), defineOptions()],
8 | optimizeDeps: {
9 | disabled: true,
10 | },
11 | test: {
12 | clearMocks: true,
13 | restoreMocks: true,
14 | environment: 'jsdom',
15 | server: {
16 | deps: { inline: ['element-plus', 'vant', 'vue3-dnd'] },
17 | },
18 | // setupFiles: ['./vitest.setup.ts'],
19 | include: ['packages/**/*.{test,spec}.{ts,tsx}'],
20 | coverage: {
21 | include: ['packages/*/src/**/*.{ts,vue}'],
22 | reporter: ['text', 'lcov'],
23 | statements: 80,
24 | },
25 | },
26 | })
27 |
--------------------------------------------------------------------------------