chore: ⬆️update deps
This commit is contained in:
parent
0c37e693c2
commit
27fe5fc752
62
.eslintrc.js
62
.eslintrc.js
|
@ -1,9 +1,11 @@
|
|||
module.exports = {
|
||||
// @ts-check
|
||||
const { defineConfig } = require('eslint-define-config');
|
||||
module.exports = defineConfig({
|
||||
root: true,
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
es6: true
|
||||
es6: true,
|
||||
},
|
||||
parser: 'vue-eslint-parser',
|
||||
parserOptions: {
|
||||
|
@ -12,36 +14,66 @@ module.exports = {
|
|||
sourceType: 'module',
|
||||
jsxPragma: 'React',
|
||||
ecmaFeatures: {
|
||||
jsx: true
|
||||
}
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
extends: [
|
||||
'plugin:vue/vue3-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'prettier',
|
||||
'plugin:prettier/recommended',
|
||||
'prettier'
|
||||
],
|
||||
rules: {
|
||||
'vue/require-default-prop': 'off',
|
||||
|
||||
'@typescript-eslint/no-unused-vars': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'vue/script-setup-uses-vars': 'error',
|
||||
'@typescript-eslint/ban-ts-ignore': 'off',
|
||||
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'@typescript-eslint/no-empty-function': 'off',
|
||||
'vue/custom-event-name-casing': 'off',
|
||||
'no-use-before-define': 'off',
|
||||
'@typescript-eslint/no-use-before-define': 'off',
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'@typescript-eslint/ban-types': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||
'@typescript-eslint/no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'no-unused-vars': [
|
||||
'error',
|
||||
{
|
||||
argsIgnorePattern: '^_',
|
||||
varsIgnorePattern: '^_',
|
||||
},
|
||||
],
|
||||
'space-before-function-paren': 'off',
|
||||
|
||||
'vue/attributes-order': 'off',
|
||||
'vue/one-component-per-file': 'off',
|
||||
'vue/html-closing-bracket-newline': 'off',
|
||||
'vue/max-attributes-per-line': 'off',
|
||||
'vue/multiline-html-element-content-newline': 'off',
|
||||
'vue/singleline-html-element-content-newline': 'off',
|
||||
'vue/attribute-hyphenation': 'off',
|
||||
'vue/require-default-prop': 'off',
|
||||
'vue/require-explicit-emits': 'off',
|
||||
'vue/html-self-closing': [
|
||||
'error',
|
||||
{
|
||||
html: {
|
||||
void: 'always',
|
||||
normal: 'never',
|
||||
component: 'always'
|
||||
component: 'always',
|
||||
},
|
||||
svg: 'always',
|
||||
math: 'always'
|
||||
}
|
||||
]
|
||||
math: 'always',
|
||||
},
|
||||
],
|
||||
'vue/multi-word-component-names': 'off',
|
||||
},
|
||||
settings: {}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,30 +1,35 @@
|
|||
name: deploy
|
||||
|
||||
name: syncToGitee
|
||||
env:
|
||||
# 7 GiB by default on GitHub, setting to 6 GiB
|
||||
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources
|
||||
NODE_OPTIONS: --max-old-space-size=6144
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
repo-sync:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Checkout Code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
# Full git history is needed to get a proper list of changed files within `super-linter`
|
||||
fetch-depth: 0
|
||||
- name: Setup Node.js v14.x
|
||||
uses: actions/setup-node@v1
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: '14.x'
|
||||
|
||||
- name: Setup pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
with:
|
||||
version: latest
|
||||
|
||||
- name: Install
|
||||
run: yarn install --frozen-lockfile
|
||||
run: pnpm install
|
||||
|
||||
- name: Build
|
||||
run: yarn build
|
||||
run: pnpm build
|
||||
|
||||
- name: Deploy
|
||||
uses: peaceiris/actions-gh-pages@v3
|
||||
|
@ -33,3 +38,28 @@ jobs:
|
|||
personal_token: ${{ secrets.PERSONAL_TOKEN }}
|
||||
commit_message: Update ghPages
|
||||
force_orphan: true
|
||||
|
||||
- name: Mirror the Github organization repos to Gitee.
|
||||
uses: Yikun/hub-mirror-action@master
|
||||
with:
|
||||
src: 'github/buqiyuan'
|
||||
dst: 'gitee/buqiyuan'
|
||||
dst_key: ${{ secrets.GITEE_PRIVATE_KEY }}
|
||||
dst_token: ${{ secrets.GITEE_TOKEN }}
|
||||
static_list: 'vite-vue3-lowcode'
|
||||
force_update: true
|
||||
debug: true
|
||||
|
||||
- name: Build Gitee Pages
|
||||
uses: yanglbme/gitee-pages-action@main
|
||||
with:
|
||||
# 注意替换为你的 Gitee 用户名
|
||||
gitee-username: buqiyuan
|
||||
# 注意在 Settings->Secrets 配置 GITEE_PASSWORD
|
||||
gitee-password: ${{ secrets.GITEE_PASSWORD }}
|
||||
# 注意替换为你的 Gitee 仓库,仓库名严格区分大小写,请准确填写,否则会出错
|
||||
gitee-repo: buqiyuan/vite-vue3-lowcode
|
||||
# 是否强制使用 HTTPS
|
||||
https: false
|
||||
# 要部署的分支,默认是 master,若是其他分支,则需要指定(指定的分支必须存在)
|
||||
branch: gh-pages
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
module.exports = {
|
||||
'*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'],
|
||||
'{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': ['prettier --write--parser json'],
|
||||
'package.json': ['prettier --write'],
|
||||
'*.vue': ['eslint --fix', 'prettier --write', 'stylelint --fix'],
|
||||
'*.{scss,less,styl,html}': ['stylelint --fix', 'prettier --write'],
|
||||
'*.md': ['prettier --write']
|
||||
}
|
|
@ -6,5 +6,3 @@
|
|||
|
||||
# Format and submit code according to lintstagedrc.js configuration
|
||||
npm run lint:lint-staged
|
||||
|
||||
npm run lint:pretty
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"octref.vetur",
|
||||
"johnsoncodehk.volar",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"stylelint.vscode-stylelint",
|
||||
"esbenp.prettier-vscode",
|
||||
|
|
|
@ -1,32 +1,10 @@
|
|||
{
|
||||
"cSpell.words": ["windi"],
|
||||
"typescript.tsdk": "./node_modules/typescript/lib",
|
||||
"volar.tsPlugin": true,
|
||||
"volar.tsPluginStatus": false,
|
||||
//===========================================
|
||||
//============= Editor ======================
|
||||
//===========================================
|
||||
"explorer.openEditors.visible": 0,
|
||||
"npm.packageManager": "pnpm",
|
||||
"editor.tabSize": 2,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"diffEditor.ignoreTrimWhitespace": false,
|
||||
"editor.trimAutoWhitespace": true,
|
||||
//===========================================
|
||||
//============= Other =======================
|
||||
//===========================================
|
||||
"breadcrumbs.enabled": true,
|
||||
"open-in-browser.default": "chrome",
|
||||
//===========================================
|
||||
//============= emmet =======================
|
||||
//===========================================
|
||||
"emmet.triggerExpansionOnTab": true,
|
||||
"emmet.showAbbreviationSuggestions": true,
|
||||
//===========================================
|
||||
//============= files =======================
|
||||
//===========================================
|
||||
"files.trimTrailingWhitespace": true,
|
||||
"files.insertFinalNewline": true,
|
||||
"files.trimFinalNewlines": true,
|
||||
"files.eol": "\n",
|
||||
"search.exclude": {
|
||||
"**/node_modules": true,
|
||||
|
@ -49,9 +27,14 @@
|
|||
"CHANGELOG.md": true,
|
||||
"examples": true,
|
||||
"res": true,
|
||||
"screenshots": true
|
||||
"screenshots": true,
|
||||
"yarn-error.log": true,
|
||||
"**/.yarn": true
|
||||
},
|
||||
"files.exclude": {
|
||||
"**/.cache": true,
|
||||
"**/.editorconfig": true,
|
||||
"**/.eslintcache": true,
|
||||
"**/bower_components": true,
|
||||
"**/.idea": true,
|
||||
"**/tmp": true,
|
||||
|
@ -73,26 +56,9 @@
|
|||
},
|
||||
"stylelint.enable": true,
|
||||
"stylelint.packageManager": "yarn",
|
||||
// ===========================================
|
||||
// ================ Vetur ====================
|
||||
// ===========================================
|
||||
"vetur.experimental.templateInterpolationService": true,
|
||||
"vetur.format.options.tabSize": 2,
|
||||
"vetur.languageFeatures.codeActions": false,
|
||||
"vetur.format.defaultFormatterOptions": {
|
||||
"js-beautify-html": {
|
||||
"wrap_attributes": "force-expand-multiline"
|
||||
}
|
||||
},
|
||||
"liveServer.settings.donotShowInfoMsg": true,
|
||||
"telemetry.enableCrashReporter": false,
|
||||
"workbench.settings.enableNaturalLanguageSearch": false,
|
||||
"path-intellisense.mappings": {
|
||||
"@/": "${workspaceRoot}/src"
|
||||
"/@/": "${workspaceRoot}/src"
|
||||
},
|
||||
"prettier.requireConfig": true,
|
||||
"typescript.updateImportsOnFileMove.enabled": "always",
|
||||
"workbench.sideBar.location": "left",
|
||||
"[javascriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
|
@ -112,11 +78,7 @@
|
|||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[scss]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": false,
|
||||
"source.fixAll.stylelint": true
|
||||
}
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[markdown]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
|
@ -126,16 +88,23 @@
|
|||
},
|
||||
"[vue]": {
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": false,
|
||||
"source.fixAll.stylelint": true
|
||||
"source.fixAll.eslint": false
|
||||
}
|
||||
},
|
||||
"i18n-ally.localesPaths": ["src/locales/lang"],
|
||||
"i18n-ally.localesPaths": [
|
||||
"src/locales/lang"
|
||||
],
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.sortKeys": true,
|
||||
"i18n-ally.namespace": true,
|
||||
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
|
||||
"i18n-ally.enabledParsers": ["ts"],
|
||||
"i18n-ally.enabledParsers": [
|
||||
"ts"
|
||||
],
|
||||
"i18n-ally.sourceLanguage": "en",
|
||||
"i18n-ally.enabledFrameworks": ["vue", "react"]
|
||||
"i18n-ally.displayLanguage": "zh-CN",
|
||||
"i18n-ally.enabledFrameworks": [
|
||||
"vue",
|
||||
"react"
|
||||
],
|
||||
}
|
||||
|
|
62
README.md
62
README.md
|
@ -14,6 +14,25 @@ or
|
|||
git clone --depth=1 https://github.com/buqiyuan/vite-vue3-lowcode.git
|
||||
```
|
||||
|
||||
```bash
|
||||
cd vite-vue3-lowcode
|
||||
|
||||
pnpm install
|
||||
|
||||
```
|
||||
|
||||
- run
|
||||
|
||||
```bash
|
||||
pnpm serve
|
||||
```
|
||||
|
||||
- build
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
## 技术栈
|
||||
|
||||
- 编程语言:[TypeScript 4.x](https://www.typescriptlang.org/zh/) + [JavaScript](https://www.javascript.com/)
|
||||
|
@ -43,8 +62,7 @@ git clone --depth=1 https://github.com/buqiyuan/vite-vue3-lowcode.git
|
|||
|
||||
### 简易说明
|
||||
|
||||
目前在使用表单时,需要把相关的`表单控件`放到`表单容器`内部,并且需要将`按钮`放到`表单容器`内,
|
||||
然后再讲`按钮的type`设置为`表单提交按钮`这时候点击提交按钮才会自动收集表单容器内部的所有字段和值
|
||||
目前在使用表单时,需要把相关的`表单控件`放到`表单容器`内部,并且需要将`按钮`放到`表单容器`内,然后再讲`按钮的type`设置为`表单提交按钮`这时候点击提交按钮才会自动收集表单容器内部的所有字段和值
|
||||
|
||||
### 快速生成组件属性
|
||||
|
||||
|
@ -53,41 +71,41 @@ git clone --depth=1 https://github.com/buqiyuan/vite-vue3-lowcode.git
|
|||
let propObj = {
|
||||
string: (config) => `createEditorInputProp(${JSON.stringify(config)})`,
|
||||
number: (config) => `createEditorInputNumberProp(${JSON.stringify(config)})`,
|
||||
boolean: (config) => `createEditorSwitchProp(${JSON.stringify(config)})`
|
||||
}
|
||||
boolean: (config) => `createEditorSwitchProp(${JSON.stringify(config)})`,
|
||||
};
|
||||
|
||||
JSON.stringify(
|
||||
$$('#props + table tbody tr').reduce((prev, curr) => {
|
||||
const children = curr.children
|
||||
const key = children[0].textContent.replace(/-([a-z])/g, (all, i) => i.toUpperCase())
|
||||
const child3Text = children[3].textContent
|
||||
const children = curr.children;
|
||||
const key = children[0].textContent.replace(/-([a-z])/g, (all, i) => i.toUpperCase());
|
||||
const child3Text = children[3].textContent;
|
||||
const defaultValue = ['true', 'false'].includes(child3Text)
|
||||
? child3Text
|
||||
: `'${child3Text == '-' ? '' : child3Text}'`
|
||||
: `'${child3Text == '-' ? '' : child3Text}'`;
|
||||
const value = (propObj[children[2].textContent] ?? propObj['string'])({
|
||||
label: `'${children[1].textContent}'`,
|
||||
defaultValue
|
||||
}).replaceAll('"', '')
|
||||
prev[key] = value
|
||||
return prev
|
||||
}, {})
|
||||
).replaceAll('"', '')
|
||||
defaultValue,
|
||||
}).replaceAll('"', '');
|
||||
prev[key] = value;
|
||||
return prev;
|
||||
}, {}),
|
||||
).replaceAll('"', '');
|
||||
```
|
||||
|
||||
```javascript
|
||||
// 在vant文档中 chrome控制台输入以下代码,快速生成组件事件
|
||||
JSON.stringify(
|
||||
$$('#events + table tbody tr').reduce((prev, curr) => {
|
||||
const children = curr.children
|
||||
const children = curr.children;
|
||||
const event = {
|
||||
label: children[1].textContent,
|
||||
value: children[0].textContent
|
||||
}
|
||||
return prev.concat([event])
|
||||
}, [])
|
||||
value: children[0].textContent,
|
||||
};
|
||||
return prev.concat([event]);
|
||||
}, []),
|
||||
)
|
||||
.replaceAll(/(?<!:)\"(?!,|})/g, '')
|
||||
.replace(/\"/g, "'")
|
||||
.replace(/\"/g, "'");
|
||||
```
|
||||
|
||||
## 部分功能演示
|
||||
|
@ -101,8 +119,8 @@ JSON.stringify(
|
|||
支持现代浏览器, 不支持 IE
|
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
|
||||
| :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: | :-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
|
||||
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
||||
| :-: | :-: | :-: | :-: | :-: |
|
||||
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
||||
|
||||
### 提交规范
|
||||
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
// Generated by 'unplugin-auto-import'
|
||||
// We suggest you to commit this file into source control
|
||||
declare global {}
|
||||
export {};
|
|
@ -7,6 +7,7 @@ module.exports = {
|
|||
'header-max-length': [2, 'always', 108],
|
||||
'subject-empty': [2, 'never'],
|
||||
'type-empty': [2, 'never'],
|
||||
'subject-case': [0],
|
||||
'type-enum': [
|
||||
2,
|
||||
'always',
|
||||
|
@ -25,8 +26,8 @@ module.exports = {
|
|||
'wip',
|
||||
'workflow',
|
||||
'types',
|
||||
'release'
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
'release',
|
||||
],
|
||||
],
|
||||
},
|
||||
};
|
||||
|
|
|
@ -3,28 +3,28 @@
|
|||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
ElAside: typeof import('element-plus/es')['ElAside']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElCol: typeof import('element-plus/es')['ElCol']
|
||||
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
||||
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider']
|
||||
ElContainer: typeof import('element-plus/es')['ElContainer']
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
ElHeader: typeof import('element-plus/es')['ElHeader']
|
||||
ElMain: typeof import('element-plus/es')['ElMain']
|
||||
ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm']
|
||||
ElPopover: typeof import('element-plus/es')['ElPopover']
|
||||
ElRow: typeof import('element-plus/es')['ElRow']
|
||||
ElTabPane: typeof import('element-plus/es')['ElTabPane']
|
||||
ElTabs: typeof import('element-plus/es')['ElTabs']
|
||||
ElTag: typeof import('element-plus/es')['ElTag']
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip']
|
||||
ElTree: typeof import('element-plus/es')['ElTree']
|
||||
ElAside: typeof import('element-plus/es')['ElAside'];
|
||||
ElButton: typeof import('element-plus/es')['ElButton'];
|
||||
ElCol: typeof import('element-plus/es')['ElCol'];
|
||||
ElCollapse: typeof import('element-plus/es')['ElCollapse'];
|
||||
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem'];
|
||||
ElConfigProvider: typeof import('element-plus/es')['ElConfigProvider'];
|
||||
ElContainer: typeof import('element-plus/es')['ElContainer'];
|
||||
ElDialog: typeof import('element-plus/es')['ElDialog'];
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown'];
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem'];
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu'];
|
||||
ElHeader: typeof import('element-plus/es')['ElHeader'];
|
||||
ElMain: typeof import('element-plus/es')['ElMain'];
|
||||
ElPopconfirm: typeof import('element-plus/es')['ElPopconfirm'];
|
||||
ElPopover: typeof import('element-plus/es')['ElPopover'];
|
||||
ElRow: typeof import('element-plus/es')['ElRow'];
|
||||
ElTabPane: typeof import('element-plus/es')['ElTabPane'];
|
||||
ElTabs: typeof import('element-plus/es')['ElTabs'];
|
||||
ElTag: typeof import('element-plus/es')['ElTag'];
|
||||
ElTooltip: typeof import('element-plus/es')['ElTooltip'];
|
||||
ElTree: typeof import('element-plus/es')['ElTree'];
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
||||
export {};
|
||||
|
|
146
package.json
146
package.json
|
@ -2,84 +2,92 @@
|
|||
"name": "vite-vue3-lowcode",
|
||||
"version": "0.0.1",
|
||||
"private": false,
|
||||
"description": "A Vite2.x + Vue3 + TypeScript LowCode",
|
||||
"description": "A Vite2.x + Vue3.x + TypeScript LowCode",
|
||||
"scripts": {
|
||||
"bootstrap": "pnpm install",
|
||||
"serve": "npm run dev",
|
||||
"dev": "cross-env --max_old_space_size=4096 vite",
|
||||
"build": "cross-env vite build",
|
||||
"build:no-cache": "pnpm clean:cache && npm run build",
|
||||
"build-tsc": "vue-tsc --noEmit && vite build",
|
||||
"serve": "vite preview",
|
||||
"preview": "npm run build && vite preview",
|
||||
"preview:dist": "vite preview",
|
||||
"log": "conventional-changelog -p angular -i CHANGELOG.md -s",
|
||||
"clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite",
|
||||
"clean:lib": "rimraf node_modules",
|
||||
"lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
|
||||
"lint:prettier": "prettier --write \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
|
||||
"lint:stylelint": "stylelint --cache --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
|
||||
"lint:lint-staged": "lint-staged",
|
||||
"deploy": "gh-pages -d dist",
|
||||
"format": "prettier --write ./src",
|
||||
"lint": "eslint ./src --ext .vue,.js,.ts,.tsx",
|
||||
"lint-fix": "eslint --fix ./src --ext .vue,.js,.ts,.tsx",
|
||||
"lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
|
||||
"lint:prettier": "prettier --write --loglevel warn \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
|
||||
"lint:stylelint": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
|
||||
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
|
||||
"lint:pretty": "pretty-quick --staged",
|
||||
"test:gzip": "npx http-server dist --cors --gzip -c-1",
|
||||
"test:br": "npx http-server dist --cors --brotli -c-1",
|
||||
"reinstall": "rimraf pnpm-lock.yaml && rimraf package.lock.json && rimraf node_modules && npm run bootstrap",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^0.2.4",
|
||||
"@vant/touch-emulator": "^1.3.2",
|
||||
"@vueuse/core": "^6.2.1",
|
||||
"@vueuse/integrations": "^6.2.1",
|
||||
"@vueuse/core": "^7.5.1",
|
||||
"@vueuse/integrations": "^7.5.1",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^0.21.4",
|
||||
"dayjs": "^1.10.6",
|
||||
"dexie": "^3.0.3",
|
||||
"element-plus": "1.1.0-beta.9",
|
||||
"axios": "^0.24.0",
|
||||
"dayjs": "^1.10.7",
|
||||
"dexie": "^3.2.0",
|
||||
"element-plus": "1.3.0-beta.1",
|
||||
"lodash": "^4.17.21",
|
||||
"monaco-editor": "^0.27.0",
|
||||
"nanoid": "^3.1.25",
|
||||
"monaco-editor": "^0.31.1",
|
||||
"nanoid": "^3.1.30",
|
||||
"normalize.css": "^8.0.1",
|
||||
"nprogress": "^1.0.0-1",
|
||||
"qrcode": "^1.4.4",
|
||||
"qs": "^6.10.1",
|
||||
"vant": "3.2.2",
|
||||
"vue": "3.2.9",
|
||||
"vue-router": "^4.0.11",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"vuex": "^4.0.2"
|
||||
"pinia": "^2.0.9",
|
||||
"qrcode": "^1.5.0",
|
||||
"qs": "^6.10.2",
|
||||
"vant": "3.3.7",
|
||||
"vue": "3.2.26",
|
||||
"vue-router": "^4.0.12",
|
||||
"vuedraggable": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^13.1.0",
|
||||
"@commitlint/config-conventional": "^13.1.0",
|
||||
"@types/lodash": "^4.14.172",
|
||||
"@types/node": "^16.7.12",
|
||||
"@typescript-eslint/eslint-plugin": "^4.31.0",
|
||||
"@typescript-eslint/parser": "^4.31.0",
|
||||
"@vitejs/plugin-legacy": "^1.5.2",
|
||||
"@vitejs/plugin-vue": "^1.6.1",
|
||||
"@vitejs/plugin-vue-jsx": "^1.1.7",
|
||||
"@vue/compiler-sfc": "3.2.9",
|
||||
"@commitlint/cli": "^16.0.1",
|
||||
"@commitlint/config-conventional": "^16.0.0",
|
||||
"@types/lodash": "^4.14.178",
|
||||
"@types/node": "^17.0.5",
|
||||
"@typescript-eslint/eslint-plugin": "^5.8.1",
|
||||
"@typescript-eslint/parser": "^5.8.1",
|
||||
"@vitejs/plugin-legacy": "^1.6.4",
|
||||
"@vitejs/plugin-vue": "^2.0.1",
|
||||
"@vitejs/plugin-vue-jsx": "^1.3.3",
|
||||
"@vue/compiler-sfc": "3.2.26",
|
||||
"commitizen": "^4.2.4",
|
||||
"cross-env": "^7.0.3",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"cz-customizable": "^6.3.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint": "^8.5.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-import": "^2.24.2",
|
||||
"eslint-define-config": "^1.2.1",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-vue": "^7.17.0",
|
||||
"eslint-plugin-vue": "^8.2.0",
|
||||
"gh-pages": "^3.2.3",
|
||||
"husky": "^7.0.2",
|
||||
"lint-staged": "^11.1.2",
|
||||
"prettier": "^2.3.2",
|
||||
"pretty-quick": "^3.1.1",
|
||||
"sass": "1.39.0",
|
||||
"stylelint": "^13.13.1",
|
||||
"stylelint-config-prettier": "^8.0.2",
|
||||
"stylelint-config-standard": "^22.0.0",
|
||||
"stylelint-order": "^4.1.0",
|
||||
"stylelint-scss": "^3.20.1",
|
||||
"typescript": "^4.4.2",
|
||||
"vite": "2.5.3",
|
||||
"vite-plugin-components": "^0.13.3",
|
||||
"vite-plugin-element-plus": "^0.0.12",
|
||||
"vite-plugin-windicss": "^1.4.1",
|
||||
"vue-eslint-parser": "^7.11.0",
|
||||
"vue-tsc": "^0.3.0",
|
||||
"windicss": "^3.1.7"
|
||||
"husky": "^7.0.4",
|
||||
"lint-staged": "^12.1.4",
|
||||
"postcss-html": "^1.3.0",
|
||||
"prettier": "^2.5.1",
|
||||
"sass": "1.45.2",
|
||||
"stylelint": "^14.2.0",
|
||||
"stylelint-config-html": "^1.0.0",
|
||||
"stylelint-config-prettier": "^9.0.3",
|
||||
"stylelint-config-recommended": "^6.0.0",
|
||||
"stylelint-config-standard": "^24.0.0",
|
||||
"stylelint-order": "^5.0.0",
|
||||
"stylelint-scss": "^4.1.0",
|
||||
"typescript": "^4.5.4",
|
||||
"unplugin-auto-import": "^0.5.5",
|
||||
"unplugin-vue-components": "^0.17.11",
|
||||
"vite": "2.7.10",
|
||||
"vite-plugin-windicss": "^1.6.1",
|
||||
"vue-eslint-parser": "^8.0.1",
|
||||
"windicss": "^3.4.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -97,7 +105,31 @@
|
|||
"url": "https://github.com/buqiyuan/vite-vue3-lowcode/issues"
|
||||
},
|
||||
"homepage": "https://github.com/buqiyuan/vite-vue3-lowcode#readme",
|
||||
"engines": {
|
||||
"node": "^12 || >=14"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{vue,js,ts,tsx}": "eslint --fix"
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
],
|
||||
"{!(package)*.json,*.code-snippets,.!(browserslist)*rc}": [
|
||||
"prettier --write--parser json"
|
||||
],
|
||||
"package.json": [
|
||||
"prettier --write"
|
||||
],
|
||||
"*.vue": [
|
||||
"eslint --fix",
|
||||
"prettier --write",
|
||||
"stylelint --fix"
|
||||
],
|
||||
"*.{scss,less,styl,html}": [
|
||||
"stylelint --fix",
|
||||
"prettier --write"
|
||||
],
|
||||
"*.md": [
|
||||
"prettier --write"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,9 +1,10 @@
|
|||
module.exports = {
|
||||
useTabs: false,
|
||||
tabWidth: 2,
|
||||
printWidth: 100,
|
||||
semi: true,
|
||||
vueIndentScriptAndStyle: true,
|
||||
singleQuote: true,
|
||||
trailingComma: 'none',
|
||||
bracketSpacing: true,
|
||||
semi: false
|
||||
}
|
||||
trailingComma: 'all',
|
||||
proseWrap: 'never',
|
||||
htmlWhitespaceSensitivity: 'strict',
|
||||
endOfLine: 'auto',
|
||||
};
|
||||
|
|
|
@ -6,27 +6,27 @@
|
|||
* @Description: axios简单的封装
|
||||
* @FilePath: \vite-vue3-lowcode\src\utils\http\request.ts
|
||||
*/
|
||||
import axios, { AxiosRequestConfig } from 'axios'
|
||||
import qs from 'qs'
|
||||
import store from '@/store'
|
||||
import { Toast } from 'vant'
|
||||
import router from '@/router'
|
||||
import { ContentTypeEnum } from './httpEnum'
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
import qs from 'qs';
|
||||
// import store from '@/store'
|
||||
import { Toast } from 'vant';
|
||||
import router from '@/router';
|
||||
import { ContentTypeEnum } from './httpEnum';
|
||||
|
||||
// create an axios instance
|
||||
const service = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_URL as string, // url = base api url + request url
|
||||
withCredentials: true, // send cookies when cross-domain requests
|
||||
timeout: 10000 // request timeout
|
||||
})
|
||||
timeout: 10000, // request timeout
|
||||
});
|
||||
interface CustomAxiosRequestConfig extends AxiosRequestConfig {
|
||||
hideLoading?: boolean
|
||||
hideLoading?: boolean;
|
||||
}
|
||||
|
||||
interface BaseResponse<T = any> {
|
||||
code: number
|
||||
data: T
|
||||
msg: string
|
||||
code: number;
|
||||
data: T;
|
||||
msg: string;
|
||||
}
|
||||
|
||||
// request拦截器 request interceptor
|
||||
|
@ -36,68 +36,67 @@ service.interceptors.request.use(
|
|||
if (!config.hideLoading) {
|
||||
// loading
|
||||
Toast.loading({
|
||||
forbidClick: true
|
||||
})
|
||||
forbidClick: true,
|
||||
});
|
||||
}
|
||||
console.log(store.getters, "store.getters['user']")
|
||||
if (store.getters['user/token']) {
|
||||
config.headers['X-Token'] = store.getters['user/token']
|
||||
}
|
||||
const contentType = config.headers['content-type'] || config.headers['Content-Type']
|
||||
const data = config.data
|
||||
// if (store.getters['user/token'] && config.headers) {
|
||||
// config.headers['X-Token'] = store.getters['user/token']
|
||||
// }
|
||||
const contentType = config.headers?.['content-type'] || config.headers?.['Content-Type'];
|
||||
const data = config.data;
|
||||
if (config.method?.toLocaleUpperCase() == 'POST' && data) {
|
||||
if (ContentTypeEnum.FORM_DATA == contentType) {
|
||||
const fd = new FormData()
|
||||
Object.keys(data).forEach((key) => fd.append(key, data[key]))
|
||||
config.data = fd
|
||||
const fd = new FormData();
|
||||
Object.keys(data).forEach((key) => fd.append(key, data[key]));
|
||||
config.data = fd;
|
||||
} else if (ContentTypeEnum.FORM_URLENCODED == contentType) {
|
||||
config.data = qs.stringify(config.data)
|
||||
config.data = qs.stringify(config.data);
|
||||
}
|
||||
}
|
||||
return config
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
// do something with request error
|
||||
console.log(error) // for debug
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
console.log(error); // for debug
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
// respone拦截器
|
||||
service.interceptors.response.use(
|
||||
(response) => {
|
||||
Toast.clear()
|
||||
const res = response.data
|
||||
Toast.clear();
|
||||
const res = response.data;
|
||||
if (res.code && res.code !== 0) {
|
||||
// 登录超时,重新登录
|
||||
if (res.code === 401) {
|
||||
// store.dispatch('FedLogOut').then(() => {
|
||||
// location.reload()
|
||||
// })
|
||||
router.replace('/error')
|
||||
router.replace('/error');
|
||||
} else {
|
||||
Toast(res.msg || '服务器访问出错了~')
|
||||
Toast(res.msg || '服务器访问出错了~');
|
||||
}
|
||||
return Promise.reject(res || 'error')
|
||||
return Promise.reject(res || 'error');
|
||||
} else {
|
||||
return Promise.resolve(response)
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
},
|
||||
(error: Error) => {
|
||||
if (error.message?.includes('timeout')) {
|
||||
Toast('请求超时!')
|
||||
Toast('请求超时!');
|
||||
}
|
||||
console.log('err' + error) // for debug
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
console.log('err' + error); // for debug
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
|
||||
const request = <T = any>(config: CustomAxiosRequestConfig): Promise<BaseResponse<T>> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
service
|
||||
.request<BaseResponse<T>>(config)
|
||||
.then((res) => resolve(res.data))
|
||||
.catch((err) => reject(err))
|
||||
})
|
||||
}
|
||||
.catch((err) => reject(err));
|
||||
});
|
||||
};
|
||||
|
||||
export default request
|
||||
export default request;
|
||||
|
|
36
src/main.ts
36
src/main.ts
|
@ -1,29 +1,31 @@
|
|||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
|
||||
import { setupElementPlus } from './plugins/element-plus'
|
||||
import { setupVant } from './plugins/vant'
|
||||
import { setupElementPlus } from './plugins/element-plus';
|
||||
import { setupVant } from './plugins/vant';
|
||||
|
||||
import 'normalize.css'
|
||||
import 'virtual:windi.css'
|
||||
import 'virtual:windi-devtools'
|
||||
import 'animate.css'
|
||||
import 'normalize.css';
|
||||
import 'virtual:windi.css';
|
||||
import 'virtual:windi-devtools';
|
||||
import 'animate.css';
|
||||
|
||||
import router from './router/'
|
||||
import store from './store/'
|
||||
import router from './router/';
|
||||
import { setupStore } from './store/';
|
||||
|
||||
const app = createApp(App)
|
||||
const app = createApp(App);
|
||||
|
||||
// 配置store
|
||||
setupStore(app);
|
||||
// 使用element-plus插件
|
||||
setupElementPlus(app)
|
||||
setupElementPlus(app);
|
||||
// 使用vant插件
|
||||
setupVant(app)
|
||||
setupVant(app);
|
||||
|
||||
app.config.globalProperties.$$refs = {}
|
||||
app.config.globalProperties.$$refs = {};
|
||||
|
||||
// if (import.meta.env.DEV) {
|
||||
window.$$refs = app.config.globalProperties.$$refs
|
||||
window.$$refs = app.config.globalProperties.$$refs;
|
||||
// }
|
||||
app.use(router).use(store)
|
||||
app.use(router);
|
||||
// 路由准备完毕再挂载
|
||||
router.isReady().then(() => app.mount('#app'))
|
||||
router.isReady().then(() => app.mount('#app'));
|
||||
|
|
|
@ -6,14 +6,15 @@
|
|||
* @Description: 图片组件
|
||||
* @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\image\index.tsx
|
||||
*/
|
||||
import { Image } from 'vant'
|
||||
import { Image } from 'vant';
|
||||
import {
|
||||
createEditorInputProp,
|
||||
createEditorSelectProp,
|
||||
createEditorSwitchProp
|
||||
} from '@/visual-editor/visual-editor.props'
|
||||
import type { VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
|
||||
import { useGlobalProperties } from '@/hooks/useGlobalProperties'
|
||||
createEditorSwitchProp,
|
||||
} from '@/visual-editor/visual-editor.props';
|
||||
import type { VisualEditorComponent } from '@/visual-editor/visual-editor.utils';
|
||||
import { useGlobalProperties } from '@/hooks/useGlobalProperties';
|
||||
import { Picture } from '@element-plus/icons-vue';
|
||||
|
||||
export default {
|
||||
key: 'image',
|
||||
|
@ -21,28 +22,30 @@ export default {
|
|||
label: '图片',
|
||||
resize: {
|
||||
width: true,
|
||||
height: true
|
||||
height: true,
|
||||
},
|
||||
preview: () => (
|
||||
<div style="text-align:center;">
|
||||
<div style="font-size:20px;background-color:#f2f2f2;color:#ccc;display:inline-flex;width:100px;height:50px;align-items:center;justify-content:center">
|
||||
<i class="el-icon-picture" />
|
||||
<el-icon>
|
||||
<Picture></Picture>
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
render: ({ props, block, styles }) => {
|
||||
const { registerRef } = useGlobalProperties()
|
||||
const { registerRef } = useGlobalProperties();
|
||||
|
||||
return () => (
|
||||
<div style={styles}>
|
||||
<Image ref={(el) => registerRef(el, block._vid)} {...props} />
|
||||
</div>
|
||||
)
|
||||
);
|
||||
},
|
||||
props: {
|
||||
src: createEditorInputProp({
|
||||
label: '图片链接',
|
||||
defaultValue: 'https://img.yzcdn.cn/vant/cat.jpeg'
|
||||
defaultValue: 'https://img.yzcdn.cn/vant/cat.jpeg',
|
||||
}),
|
||||
width: createEditorInputProp({ label: '宽度', defaultValue: 100 }),
|
||||
height: createEditorInputProp({ label: '高度', defaultValue: 100 }),
|
||||
|
@ -52,46 +55,46 @@ export default {
|
|||
options: [
|
||||
{
|
||||
label: '保持宽高缩放图片,使图片的长边能完全显示出来',
|
||||
value: 'contain'
|
||||
value: 'contain',
|
||||
},
|
||||
{
|
||||
label: '保持宽高缩放图片,使图片的短边能完全显示出来,裁剪长边',
|
||||
value: 'cover'
|
||||
value: 'cover',
|
||||
},
|
||||
{
|
||||
label: '拉伸图片,使图片填满元素',
|
||||
value: 'fill'
|
||||
value: 'fill',
|
||||
},
|
||||
{
|
||||
label: '保持图片原有尺寸',
|
||||
value: 'none'
|
||||
value: 'none',
|
||||
},
|
||||
{
|
||||
label: '取 none 或 contain 中较小的一个',
|
||||
value: 'scale-down'
|
||||
}
|
||||
value: 'scale-down',
|
||||
},
|
||||
],
|
||||
defaultValue: 'fill'
|
||||
defaultValue: 'fill',
|
||||
}),
|
||||
iconPrefix: createEditorInputProp({
|
||||
label: '图标类名前缀',
|
||||
tips: '图标类名前缀,同 Icon 组件的 class-prefix 属性'
|
||||
tips: '图标类名前缀,同 Icon 组件的 class-prefix 属性',
|
||||
}),
|
||||
iconSize: createEditorInputProp({ label: '加载图标和失败图标的大小' }),
|
||||
lazyLoad: createEditorSwitchProp({
|
||||
label: '是否开启图片懒加载',
|
||||
tips: '须配合 Lazyload 组件使用'
|
||||
tips: '须配合 Lazyload 组件使用',
|
||||
}),
|
||||
loadingIcon: createEditorInputProp({ label: '加载时提示的图标名称或图片链接' }),
|
||||
radius: createEditorInputProp({ label: '圆角大小', tips: '默认单位为 px' }),
|
||||
round: createEditorSwitchProp({ label: '是否显示为圆形' }),
|
||||
'show-error': createEditorSwitchProp({ label: '是否展示图片加载失败提示' }),
|
||||
'show-loading': createEditorSwitchProp({ label: '是否展示图片加载中提示' }),
|
||||
alt: createEditorInputProp({ label: '替代文本' })
|
||||
alt: createEditorInputProp({ label: '替代文本' }),
|
||||
},
|
||||
events: [
|
||||
{ label: '点击图片时触发', value: 'click' },
|
||||
{ label: '图片加载完毕时触发', value: 'load' },
|
||||
{ label: '图片加载失败时触发', value: 'error' }
|
||||
]
|
||||
} as VisualEditorComponent
|
||||
{ label: '图片加载失败时触发', value: 'error' },
|
||||
],
|
||||
} as VisualEditorComponent;
|
||||
|
|
|
@ -6,16 +6,16 @@
|
|||
* @Description: 表单项类型 - 评分
|
||||
* @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\rate\index.tsx
|
||||
*/
|
||||
import { Field, Rate } from 'vant'
|
||||
import type { VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
|
||||
import { createFieldProps } from './createFieldProps'
|
||||
import { useGlobalProperties } from '@/hooks/useGlobalProperties'
|
||||
import { Field, Rate } from 'vant';
|
||||
import type { VisualEditorComponent } from '@/visual-editor/visual-editor.utils';
|
||||
import { createFieldProps } from './createFieldProps';
|
||||
import { useGlobalProperties } from '@/hooks/useGlobalProperties';
|
||||
import {
|
||||
createEditorInputNumberProp,
|
||||
createEditorInputProp,
|
||||
createEditorModelBindProp,
|
||||
createEditorSwitchProp
|
||||
} from '@/visual-editor/visual-editor.props'
|
||||
createEditorSwitchProp,
|
||||
} from '@/visual-editor/visual-editor.props';
|
||||
|
||||
export default {
|
||||
key: 'rate',
|
||||
|
@ -30,7 +30,7 @@ export default {
|
|||
></Field>
|
||||
),
|
||||
render: ({ styles, block, props }) => {
|
||||
const { registerRef } = useGlobalProperties()
|
||||
const { registerRef } = useGlobalProperties();
|
||||
|
||||
return () => (
|
||||
<div style={styles}>
|
||||
|
@ -45,25 +45,25 @@ export default {
|
|||
{...props}
|
||||
v-model={props.modelValue}
|
||||
></Rate>
|
||||
)
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
},
|
||||
props: {
|
||||
modelValue: createEditorInputNumberProp({ label: '默认值', defaultValue: 0 }),
|
||||
name: createEditorModelBindProp({ label: '字段绑定', defaultValue: '' }),
|
||||
label: createEditorInputProp({ label: '输入框左侧文本', defaultValue: '评分' }),
|
||||
count: createEditorInputNumberProp({ label: '图标总数' }),
|
||||
count: createEditorInputNumberProp({ label: '图标总数', defaultValue: 5 }),
|
||||
size: createEditorInputProp({ label: '图标大小' }),
|
||||
'allow-half': createEditorSwitchProp({ label: '是否允许半选' }),
|
||||
...createFieldProps()
|
||||
...createFieldProps(),
|
||||
},
|
||||
resize: {
|
||||
width: true
|
||||
width: true,
|
||||
},
|
||||
model: {
|
||||
default: '绑定字段'
|
||||
}
|
||||
} as VisualEditorComponent
|
||||
default: '绑定字段',
|
||||
},
|
||||
} as VisualEditorComponent;
|
||||
|
|
|
@ -6,16 +6,17 @@
|
|||
* @Description: 表单项类型 - 滑块
|
||||
* @FilePath: \vite-vue3-lowcode\src\packages\base-widgets\slider\index.tsx
|
||||
*/
|
||||
import { Field, Slider } from 'vant'
|
||||
import type { VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
|
||||
import { createFieldProps } from './createFieldProps'
|
||||
import { useGlobalProperties } from '@/hooks/useGlobalProperties'
|
||||
import { Field, Slider } from 'vant';
|
||||
import type { VisualEditorComponent } from '@/visual-editor/visual-editor.utils';
|
||||
import { createFieldProps } from './createFieldProps';
|
||||
import { useGlobalProperties } from '@/hooks/useGlobalProperties';
|
||||
import {
|
||||
createEditorInputNumberProp,
|
||||
createEditorInputProp,
|
||||
createEditorModelBindProp,
|
||||
createEditorSwitchProp
|
||||
} from '@/visual-editor/visual-editor.props'
|
||||
createEditorSwitchProp,
|
||||
} from '@/visual-editor/visual-editor.props';
|
||||
import { omit } from 'lodash';
|
||||
|
||||
export default {
|
||||
key: 'slider',
|
||||
|
@ -30,13 +31,12 @@ export default {
|
|||
></Field>
|
||||
),
|
||||
render: ({ styles, block, props }) => {
|
||||
const { registerRef } = useGlobalProperties()
|
||||
const { registerRef } = useGlobalProperties();
|
||||
|
||||
return () => (
|
||||
<div style={styles}>
|
||||
<Field
|
||||
{...props}
|
||||
modelValue={''}
|
||||
{...omit(props, 'size')}
|
||||
name={Array.isArray(props.name) ? [...props.name].pop() : props.name}
|
||||
v-slots={{
|
||||
input: () => (
|
||||
|
@ -45,26 +45,26 @@ export default {
|
|||
{...props}
|
||||
v-model={props.modelValue}
|
||||
></Slider>
|
||||
)
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
);
|
||||
},
|
||||
props: {
|
||||
modelValue: createEditorInputNumberProp({ label: '默认值', defaultValue: 0 }),
|
||||
name: createEditorModelBindProp({ label: '字段绑定', defaultValue: '' }),
|
||||
label: createEditorInputProp({ label: '输入框左侧文本', defaultValue: '滑块' }),
|
||||
min: createEditorInputNumberProp({ label: '最小值' }),
|
||||
max: createEditorInputNumberProp({ label: '最大值' }),
|
||||
max: createEditorInputNumberProp({ label: '最大值', defaultValue: 10 }),
|
||||
size: createEditorInputNumberProp({ label: '图标大小' }),
|
||||
range: createEditorSwitchProp({ label: '是否开启双滑块模式' }),
|
||||
...createFieldProps()
|
||||
...createFieldProps(),
|
||||
},
|
||||
resize: {
|
||||
width: true
|
||||
width: true,
|
||||
},
|
||||
model: {
|
||||
default: '绑定字段'
|
||||
}
|
||||
} as VisualEditorComponent
|
||||
default: '绑定字段',
|
||||
},
|
||||
} as VisualEditorComponent;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { App, Component } from 'vue'
|
||||
import 'element-plus/dist/index.css'
|
||||
import { App, Component } from 'vue';
|
||||
import 'element-plus/dist/index.css';
|
||||
|
||||
import {
|
||||
ElAffix,
|
||||
ElSkeleton,
|
||||
|
@ -32,8 +33,8 @@ import {
|
|||
ElAlert,
|
||||
ElRadioButton,
|
||||
ElRadioGroup,
|
||||
ElInfiniteScroll
|
||||
} from 'element-plus'
|
||||
ElInfiniteScroll,
|
||||
} from 'element-plus';
|
||||
|
||||
const components = [
|
||||
ElAffix,
|
||||
|
@ -65,16 +66,16 @@ const components = [
|
|||
ElPagination,
|
||||
ElAlert,
|
||||
ElRadioButton,
|
||||
ElRadioGroup
|
||||
]
|
||||
ElRadioGroup,
|
||||
];
|
||||
|
||||
const plugins = [ElLoading, ElInfiniteScroll]
|
||||
const plugins = [ElLoading, ElInfiniteScroll];
|
||||
|
||||
export function setupElementPlus(app: App) {
|
||||
components.forEach((component: Component) => {
|
||||
app.component(component.name!, component)
|
||||
})
|
||||
app.component(component.name!, component);
|
||||
});
|
||||
plugins.forEach((plugin) => {
|
||||
app.use(plugin)
|
||||
})
|
||||
app.use(plugin);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,27 +1,10 @@
|
|||
import { createStore } from 'vuex'
|
||||
import type { App } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
|
||||
const defaultState = {
|
||||
count: 0
|
||||
const store = createPinia();
|
||||
|
||||
export function setupStore(app: App<Element>) {
|
||||
app.use(store);
|
||||
}
|
||||
|
||||
// Create a new store instance.
|
||||
export default createStore({
|
||||
state() {
|
||||
return defaultState
|
||||
},
|
||||
mutations: {
|
||||
increment(state: typeof defaultState) {
|
||||
state.count++
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
increment(context) {
|
||||
context.commit('increment')
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
double(state: typeof defaultState) {
|
||||
return 2 * state.count
|
||||
}
|
||||
}
|
||||
})
|
||||
export { store };
|
||||
|
|
|
@ -6,51 +6,52 @@
|
|||
* @Description:
|
||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\common\format-input-number\index.tsx
|
||||
*/
|
||||
import { defineComponent } from 'vue'
|
||||
import { ElInput } from 'element-plus'
|
||||
import type { PropType } from 'vue'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import styles from './index.module.scss'
|
||||
import { defineComponent } from 'vue';
|
||||
import { ElInput, ElIcon } from 'element-plus';
|
||||
import type { PropType } from 'vue';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import styles from './index.module.scss';
|
||||
import { ArrowDown, ArrowUp } from '@element-plus/icons-vue';
|
||||
|
||||
export const FormatInputNumber = defineComponent({
|
||||
props: {
|
||||
modelValue: {
|
||||
type: [String] as PropType<string>,
|
||||
default: ''
|
||||
default: '',
|
||||
},
|
||||
symbol: {
|
||||
// 符号
|
||||
type: String as PropType<string>,
|
||||
default: 'px'
|
||||
default: 'px',
|
||||
},
|
||||
max: {
|
||||
type: [Number],
|
||||
default: 100
|
||||
default: 100,
|
||||
},
|
||||
min: {
|
||||
type: [Number],
|
||||
default: 0
|
||||
}
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
emits: ['update:modelValue'],
|
||||
setup(props, { attrs }) {
|
||||
const modelValue = useVModel(props, 'modelValue')
|
||||
const modelValue = useVModel(props, 'modelValue');
|
||||
|
||||
const onInput = (val) => {
|
||||
let num = parseFloat(`${val}`.replace(/[^0-9]/gi, ''))
|
||||
num = Number.isNaN(num) ? 0 : num
|
||||
num = Math.max(props.min, num)
|
||||
num = Math.min(props.max, num)
|
||||
modelValue.value = num + props.symbol
|
||||
}
|
||||
let num = parseFloat(`${val}`.replace(/[^0-9]/gi, ''));
|
||||
num = Number.isNaN(num) ? 0 : num;
|
||||
num = Math.max(props.min, num);
|
||||
num = Math.min(props.max, num);
|
||||
modelValue.value = num + props.symbol;
|
||||
};
|
||||
|
||||
const increment = () => {
|
||||
onInput(parseFloat(modelValue.value) + 1)
|
||||
}
|
||||
onInput(parseFloat(modelValue.value) + 1);
|
||||
};
|
||||
|
||||
const cutdown = () => {
|
||||
onInput(Math.max(props.min, parseFloat(modelValue.value) - 1))
|
||||
}
|
||||
onInput(Math.max(props.min, parseFloat(modelValue.value) - 1));
|
||||
};
|
||||
|
||||
return () => (
|
||||
<div class={styles.formatInputNumber}>
|
||||
|
@ -63,13 +64,21 @@ export const FormatInputNumber = defineComponent({
|
|||
{{
|
||||
append: () => (
|
||||
<div class={'flex flex-col'}>
|
||||
<div onClick={increment} class={'el-icon-arrow-up cursor-pointer'}></div>
|
||||
<div onClick={cutdown} class={'el-icon-arrow-down cursor-pointer'}></div>
|
||||
<div onClick={increment} class={'cursor-pointer leading-0'}>
|
||||
<ElIcon size={14}>
|
||||
<ArrowUp />
|
||||
</ElIcon>
|
||||
</div>
|
||||
<div onClick={cutdown} class={'cursor-pointer leading-0'}>
|
||||
<ElIcon size={14}>
|
||||
<ArrowDown />
|
||||
</ElIcon>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
),
|
||||
}}
|
||||
</ElInput>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -14,7 +14,9 @@
|
|||
class="tool-item flex flex-col items-center cursor-pointer"
|
||||
@click="toolItem.onClick"
|
||||
>
|
||||
<i :class="toolItem.icon"></i>
|
||||
<el-icon>
|
||||
<component :is="toolItem.icon" />
|
||||
</el-icon>
|
||||
<div class="title">{{ toolItem.title }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -26,8 +28,8 @@
|
|||
<el-tooltip class="item" effect="dark" content="运行" placement="bottom">
|
||||
<el-button
|
||||
type="primary"
|
||||
size="mini"
|
||||
icon="el-icon-video-play"
|
||||
:icon="VideoPlay"
|
||||
size="large"
|
||||
circle
|
||||
class="flex-shrink-0 !p-6px"
|
||||
@click="runPreview"
|
||||
|
@ -65,66 +67,52 @@
|
|||
<preview v-model:visible="isShowH5Preview" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, reactive, toRefs } from 'vue'
|
||||
import Preview from './preview.vue'
|
||||
import { useVisualData, localKey } from '@/visual-editor/hooks/useVisualData'
|
||||
import { BASE_URL } from '@/visual-editor/utils'
|
||||
import { useTools } from './useTools'
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import Preview from './preview.vue';
|
||||
import { useVisualData, localKey } from '@/visual-editor/hooks/useVisualData';
|
||||
import { BASE_URL } from '@/visual-editor/utils';
|
||||
import { useTools } from './useTools';
|
||||
import { VideoPlay } from '@element-plus/icons-vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'Header',
|
||||
components: { Preview },
|
||||
setup() {
|
||||
const state = reactive({
|
||||
isShowH5Preview: false
|
||||
})
|
||||
const isShowH5Preview = ref(false);
|
||||
|
||||
const tools = useTools()
|
||||
const tools = useTools();
|
||||
|
||||
const { jsonData } = useVisualData()
|
||||
const { jsonData } = useVisualData();
|
||||
|
||||
const runPreview = () => {
|
||||
sessionStorage.setItem(localKey, JSON.stringify(jsonData))
|
||||
localStorage.setItem(localKey, JSON.stringify(jsonData))
|
||||
state.isShowH5Preview = true
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
BASE_URL,
|
||||
tools,
|
||||
runPreview
|
||||
}
|
||||
}
|
||||
})
|
||||
const runPreview = () => {
|
||||
sessionStorage.setItem(localKey, JSON.stringify(jsonData));
|
||||
localStorage.setItem(localKey, JSON.stringify(jsonData));
|
||||
isShowH5Preview.value = true;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.header {
|
||||
width: 100%;
|
||||
.header {
|
||||
width: 100%;
|
||||
|
||||
.logo {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background-image: url('@/assets/logo.png');
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
.logo {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
background-image: url('@/assets/logo.png');
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.tool-item {
|
||||
.title {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
.tool-item {
|
||||
.title {
|
||||
margin-top: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-button {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.right-tools > * {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.el-button {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.right-tools > * {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -5,39 +5,51 @@
|
|||
* @description:tools
|
||||
* @update: 2021/5/7 10:46
|
||||
*/
|
||||
import { reactive } from 'vue'
|
||||
import { ElMessage, ElRadio, ElRadioGroup } from 'element-plus'
|
||||
import { useQRCode } from '@vueuse/integrations'
|
||||
import { useClipboard } from '@vueuse/core'
|
||||
import { useVisualData, localKey } from '@/visual-editor/hooks/useVisualData'
|
||||
import { useModal } from '@/visual-editor/hooks/useModal'
|
||||
import MonacoEditor from '@/visual-editor/components/common/monaco-editor/MonacoEditor'
|
||||
import { reactive } from 'vue';
|
||||
import { ElMessage, ElRadio, ElRadioGroup } from 'element-plus';
|
||||
import { useQRCode } from '@vueuse/integrations';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import { useVisualData, localKey } from '@/visual-editor/hooks/useVisualData';
|
||||
import { useModal } from '@/visual-editor/hooks/useModal';
|
||||
import MonacoEditor from '@/visual-editor/components/common/monaco-editor/MonacoEditor';
|
||||
import {
|
||||
DocumentCopy,
|
||||
Cellphone,
|
||||
RefreshLeft,
|
||||
RefreshRight,
|
||||
Position,
|
||||
Delete,
|
||||
ChatLineSquare,
|
||||
Download,
|
||||
Upload,
|
||||
} from '@element-plus/icons-vue';
|
||||
import 'element-plus/es/components/message/style/css';
|
||||
|
||||
export const useTools = () => {
|
||||
const { jsonData, updatePage, currentPage, overrideProject } = useVisualData()
|
||||
const { jsonData, updatePage, currentPage, overrideProject } = useVisualData();
|
||||
const state = reactive({
|
||||
coverRadio: 'current',
|
||||
importJsonValue: ''
|
||||
})
|
||||
importJsonValue: '',
|
||||
});
|
||||
const importJsonChange = (value) => {
|
||||
state.importJsonValue = value
|
||||
}
|
||||
state.importJsonValue = value;
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
title: '导入JSON',
|
||||
icon: 'el-icon-upload2',
|
||||
icon: Upload,
|
||||
onClick: () => {
|
||||
useModal({
|
||||
title: '导入JSON',
|
||||
props: {
|
||||
width: 642
|
||||
width: 642,
|
||||
},
|
||||
content: () => (
|
||||
<>
|
||||
<ElRadioGroup v-model={state.coverRadio}>
|
||||
<ElRadio label={'current'}>覆盖当前页面</ElRadio>
|
||||
<ElRadio label={'all'}>覆盖整个项目</ElRadio>
|
||||
<ElRadio label="current">覆盖当前页面</ElRadio>
|
||||
<ElRadio label="all">覆盖整个项目</ElRadio>
|
||||
</ElRadioGroup>
|
||||
<MonacoEditor
|
||||
onChange={importJsonChange}
|
||||
|
@ -47,119 +59,119 @@ export const useTools = () => {
|
|||
</>
|
||||
),
|
||||
onConfirm: () => {
|
||||
const isCoverCurrent = state.coverRadio == 'current'
|
||||
const isCoverCurrent = state.coverRadio == 'current';
|
||||
// 覆盖当前页面
|
||||
if (isCoverCurrent) {
|
||||
updatePage({
|
||||
oldPath: currentPage.value.path,
|
||||
page: JSON.parse(state.importJsonValue)
|
||||
})
|
||||
page: JSON.parse(state.importJsonValue),
|
||||
});
|
||||
} else {
|
||||
// 覆盖整个项目
|
||||
overrideProject(JSON.parse(state.importJsonValue))
|
||||
overrideProject(JSON.parse(state.importJsonValue));
|
||||
}
|
||||
ElMessage({
|
||||
showClose: true,
|
||||
type: 'success',
|
||||
duration: 2000,
|
||||
message: isCoverCurrent ? '成功覆盖当前页面' : '成功覆盖整个项目'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
message: isCoverCurrent ? '成功覆盖当前页面' : '成功覆盖整个项目',
|
||||
});
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '导出JSON',
|
||||
icon: 'el-icon-download',
|
||||
icon: Download,
|
||||
onClick: () => {
|
||||
const { copy } = useClipboard({ source: JSON.stringify(jsonData) })
|
||||
const { copy } = useClipboard({ source: JSON.stringify(jsonData) });
|
||||
|
||||
copy()
|
||||
.then(() => ElMessage.success('复制成功'))
|
||||
.catch((err) => ElMessage.error(`复制失败:${err}`))
|
||||
}
|
||||
.catch((err) => ElMessage.error(`复制失败:${err}`));
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '真机预览',
|
||||
icon: 'el-icon-mobile-phone',
|
||||
icon: Cellphone,
|
||||
onClick: () => {
|
||||
const qrcode = useQRCode(location.origin + '/preview')
|
||||
const qrcode = useQRCode(location.origin + '/preview');
|
||||
useModal({
|
||||
title: '预览二维码(暂不可用)',
|
||||
props: {
|
||||
width: 300
|
||||
width: 300,
|
||||
},
|
||||
footer: null,
|
||||
content: () => (
|
||||
<div class={'flex justify-center'}>
|
||||
<img width={220} height={220} src={qrcode.value} />
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
),
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '复制页面',
|
||||
icon: 'el-icon-document-copy',
|
||||
icon: DocumentCopy,
|
||||
onClick: () => {
|
||||
ElMessage({
|
||||
showClose: true,
|
||||
type: 'info',
|
||||
duration: 2000,
|
||||
message: '敬请期待!'
|
||||
})
|
||||
}
|
||||
message: '敬请期待!',
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '撤销',
|
||||
icon: 'el-icon-refresh-left',
|
||||
icon: RefreshLeft,
|
||||
onClick: () => {
|
||||
ElMessage({
|
||||
showClose: true,
|
||||
type: 'info',
|
||||
duration: 2000,
|
||||
message: '敬请期待!'
|
||||
})
|
||||
}
|
||||
message: '敬请期待!',
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '重做',
|
||||
icon: 'el-icon-refresh-right',
|
||||
icon: RefreshRight,
|
||||
onClick: () => {
|
||||
ElMessage({
|
||||
showClose: true,
|
||||
type: 'info',
|
||||
duration: 2000,
|
||||
message: '敬请期待!'
|
||||
})
|
||||
}
|
||||
message: '敬请期待!',
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '清空页面',
|
||||
icon: 'el-icon-delete',
|
||||
icon: Delete,
|
||||
onClick: () => {
|
||||
ElMessage({
|
||||
showClose: true,
|
||||
type: 'info',
|
||||
duration: 2000,
|
||||
message: '敬请期待!'
|
||||
})
|
||||
}
|
||||
message: '敬请期待!',
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '预览',
|
||||
icon: 'el-icon-position',
|
||||
icon: Position,
|
||||
onClick: () => {
|
||||
localStorage.setItem(localKey, JSON.stringify(jsonData))
|
||||
window.open(location.href.replace('/#/', '/preview/#/'))
|
||||
}
|
||||
localStorage.setItem(localKey, JSON.stringify(jsonData));
|
||||
window.open(location.href.replace('/#/', '/preview/#/'));
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '反馈',
|
||||
icon: 'el-icon-chat-line-square',
|
||||
icon: ChatLineSquare,
|
||||
onClick: () => {
|
||||
window.open('https://github.com/buqiyuan/vite-vue3-lowcode/issues/new')
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
window.open('https://github.com/buqiyuan/vite-vue3-lowcode/issues/new');
|
||||
},
|
||||
},
|
||||
];
|
||||
};
|
||||
|
|
|
@ -6,30 +6,31 @@
|
|||
* @Description: 基础组件
|
||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\left-aside\components\base-widgets\index.tsx
|
||||
*/
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { visualConfig } from '@/visual.config'
|
||||
import styles from './index.module.scss'
|
||||
import { createNewBlock } from '@/visual-editor/visual-editor.utils'
|
||||
import DraggableTransitionGroup from '@/visual-editor/components/simulator-editor/draggable-transition-group.vue'
|
||||
import { defineComponent, ref } from 'vue';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { visualConfig } from '@/visual.config';
|
||||
import styles from './index.module.scss';
|
||||
import { createNewBlock } from '@/visual-editor/visual-editor.utils';
|
||||
import DraggableTransitionGroup from '@/visual-editor/components/simulator-editor/draggable-transition-group.vue';
|
||||
import { Edit } from '@element-plus/icons-vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'BaseWidgets',
|
||||
label: '基本组件',
|
||||
order: 3,
|
||||
icon: 'el-icon-edit',
|
||||
icon: Edit,
|
||||
setup() {
|
||||
const baseWidgets = ref(visualConfig.componentModules.baseWidgets)
|
||||
const baseWidgets = ref(visualConfig.componentModules.baseWidgets);
|
||||
|
||||
const log = (evt) => {
|
||||
window.console.log('onChange:', evt)
|
||||
}
|
||||
window.console.log('onChange:', evt);
|
||||
};
|
||||
// 克隆组件
|
||||
const cloneDog = (comp) => {
|
||||
console.log('当前拖拽的组件:', comp)
|
||||
const newComp = cloneDeep(comp)
|
||||
return createNewBlock(newComp)
|
||||
}
|
||||
console.log('当前拖拽的组件:', comp);
|
||||
const newComp = cloneDeep(comp);
|
||||
return createNewBlock(newComp);
|
||||
};
|
||||
|
||||
return () => (
|
||||
<>
|
||||
|
@ -46,10 +47,10 @@ export default defineComponent({
|
|||
<div class={styles.listGroupItem} data-label={element.label}>
|
||||
{element.preview()}
|
||||
</div>
|
||||
)
|
||||
),
|
||||
}}
|
||||
</DraggableTransitionGroup>
|
||||
</>
|
||||
)
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -6,29 +6,30 @@
|
|||
* @Description:
|
||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\left-aside\components\container-component\index.tsx
|
||||
*/
|
||||
import { defineComponent } from 'vue'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { visualConfig } from '@/visual.config'
|
||||
import Draggable from 'vuedraggable'
|
||||
import styles from './index.module.scss'
|
||||
import { createNewBlock } from '@/visual-editor/visual-editor.utils'
|
||||
import type { VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
|
||||
import { defineComponent } from 'vue';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { visualConfig } from '@/visual.config';
|
||||
import Draggable from 'vuedraggable';
|
||||
import styles from './index.module.scss';
|
||||
import { createNewBlock } from '@/visual-editor/visual-editor.utils';
|
||||
import type { VisualEditorComponent } from '@/visual-editor/visual-editor.utils';
|
||||
import { Suitcase } from '@element-plus/icons-vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ContainerComponent',
|
||||
label: '容器组件',
|
||||
icon: 'el-icon-suitcase',
|
||||
icon: Suitcase,
|
||||
order: 4,
|
||||
setup() {
|
||||
const log = (evt) => {
|
||||
window.console.log(evt)
|
||||
}
|
||||
window.console.log(evt);
|
||||
};
|
||||
// 克隆组件
|
||||
const cloneDog = (comp) => {
|
||||
console.log('当前拖拽的组件:', comp)
|
||||
const newComp = cloneDeep(comp)
|
||||
return createNewBlock(newComp)
|
||||
}
|
||||
console.log('当前拖拽的组件:', comp);
|
||||
const newComp = cloneDeep(comp);
|
||||
return createNewBlock(newComp);
|
||||
};
|
||||
|
||||
return () => (
|
||||
<>
|
||||
|
@ -47,10 +48,10 @@ export default defineComponent({
|
|||
<div class={styles.listGroupItem} data-label={element.label}>
|
||||
{element.preview()}
|
||||
</div>
|
||||
)
|
||||
),
|
||||
}}
|
||||
</Draggable>
|
||||
</>
|
||||
)
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
<!--业务组件-->
|
||||
<template>业务组件</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CustomComponent',
|
||||
label: '业务组件',
|
||||
order: 5,
|
||||
icon: 'el-icon-upload'
|
||||
}
|
||||
<script lang="ts">
|
||||
import { Upload } from '@element-plus/icons-vue';
|
||||
|
||||
export default {
|
||||
name: 'CustomComponent',
|
||||
label: '业务组件',
|
||||
order: 5,
|
||||
icon: Upload,
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
|
@ -8,13 +8,11 @@
|
|||
-->
|
||||
<template>
|
||||
<div class="!mb-10px">
|
||||
<el-button type="primary" size="small" @click="showModelMoal">添加</el-button>
|
||||
<el-button type="warning" size="small" @click="showImportSwaggerJsonModal"
|
||||
>导入swagger</el-button
|
||||
>
|
||||
<el-button type="primary" @click="showModelMoal">添加</el-button>
|
||||
<el-button type="warning" @click="showImportSwaggerJsonModal">导入swagger</el-button>
|
||||
<el-popconfirm title="确定要删除全部接口吗?" @confirm="updateFetchApi([], true)">
|
||||
<template #reference>
|
||||
<el-button type="danger" size="small">清空</el-button>
|
||||
<el-button type="danger">清空</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</div>
|
||||
|
@ -25,17 +23,18 @@
|
|||
<div class="model-item-title">
|
||||
<span class="truncate w-160px">{{ item.name }}</span>
|
||||
<div class="model-actions">
|
||||
<i class="el-icon-edit" @click="editApiItem(item)"></i>
|
||||
<el-icon size="24" color="#2196f3" @click.stop="editApiItem(item)">
|
||||
<Edit />
|
||||
</el-icon>
|
||||
<el-popconfirm
|
||||
confirm-button-text="确定"
|
||||
cancel-button-text="取消"
|
||||
icon="el-icon-info"
|
||||
icon-color="red"
|
||||
title="确定要删除该接口吗?"
|
||||
@confirm="deleteFetchApi(item.key)"
|
||||
>
|
||||
<template #reference>
|
||||
<i class="el-icon-delete"></i>
|
||||
<el-icon size="24" color="#f44336"><Delete /></el-icon>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</div>
|
||||
|
@ -50,229 +49,217 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { reactive, ref, computed } from 'vue'
|
||||
import {
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElSelect,
|
||||
ElOption,
|
||||
ElButton,
|
||||
ElMessage,
|
||||
ElCascader
|
||||
} from 'element-plus'
|
||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData'
|
||||
import type { FetchApiItem, VisualEditorModel } from '@/visual-editor/visual-editor.utils'
|
||||
import { useModal } from '@/visual-editor/hooks/useModal'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { generateNanoid } from '@/visual-editor/utils/'
|
||||
import { RequestEnum, ContentTypeEnum } from '@/enums/httpEnum'
|
||||
import { useImportSwaggerJsonModal } from './utils'
|
||||
import { reactive, ref, computed } from 'vue';
|
||||
import {
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElSelect,
|
||||
ElOption,
|
||||
ElButton,
|
||||
ElMessage,
|
||||
ElCascader,
|
||||
} from 'element-plus';
|
||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData';
|
||||
import type { FetchApiItem, VisualEditorModel } from '@/visual-editor/visual-editor.utils';
|
||||
import { useModal } from '@/visual-editor/hooks/useModal';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { generateNanoid } from '@/visual-editor/utils/';
|
||||
import { RequestEnum, ContentTypeEnum } from '@/enums/httpEnum';
|
||||
import { useImportSwaggerJsonModal } from './utils';
|
||||
import { Delete, Edit } from '@element-plus/icons-vue';
|
||||
|
||||
interface IState {
|
||||
activeNames: string[]
|
||||
ruleForm: FetchApiItem
|
||||
}
|
||||
|
||||
const { jsonData, incrementFetchApi, updateFetchApi, deleteFetchApi } = useVisualData()
|
||||
const { showImportSwaggerJsonModal } = useImportSwaggerJsonModal()
|
||||
/**
|
||||
* @description 接口集合
|
||||
*/
|
||||
const apis = computed(() => cloneDeep(jsonData.actions.fetch.apis))
|
||||
|
||||
/**
|
||||
* @description 模型集合
|
||||
*/
|
||||
const models = computed(() => cloneDeep(jsonData.models))
|
||||
|
||||
/**
|
||||
* @description 是否处于编辑状态
|
||||
*/
|
||||
const isEdit = computed(() => apis.value.some((item) => item.key == state.ruleForm.key))
|
||||
|
||||
/**
|
||||
* @description 创建空的数据接口对象
|
||||
*/
|
||||
const createEmptyApiItem = (): FetchApiItem => ({
|
||||
key: generateNanoid(),
|
||||
name: '',
|
||||
options: {
|
||||
url: '', // 请求的url
|
||||
method: RequestEnum.GET, // 请求的方法
|
||||
contentType: 'JSON' // 请求的内容类型
|
||||
},
|
||||
data: {
|
||||
bind: '', // 请求绑定对应的某个实体
|
||||
recv: '' // 响应的结果绑定到某个实体上
|
||||
interface IState {
|
||||
activeNames: string[];
|
||||
ruleForm: FetchApiItem;
|
||||
}
|
||||
})
|
||||
const ruleFormRef = ref<InstanceType<typeof ElForm>>()
|
||||
|
||||
const state = reactive<IState>({
|
||||
activeNames: [],
|
||||
ruleForm: createEmptyApiItem()
|
||||
})
|
||||
const { jsonData, incrementFetchApi, updateFetchApi, deleteFetchApi } = useVisualData();
|
||||
const { showImportSwaggerJsonModal } = useImportSwaggerJsonModal();
|
||||
/**
|
||||
* @description 接口集合
|
||||
*/
|
||||
const apis = computed(() => cloneDeep(jsonData.actions.fetch.apis));
|
||||
|
||||
const rules = {
|
||||
name: [{ required: true, message: '请输入接口名称', trigger: 'change' }],
|
||||
'options.url': [{ required: true, message: '请输入接口名称', trigger: 'change' }],
|
||||
'options.contentType': [{ required: true, message: '请选择内容类型', trigger: 'change' }]
|
||||
}
|
||||
/**
|
||||
* @description 模型集合
|
||||
*/
|
||||
const models = computed(() => cloneDeep(jsonData.models));
|
||||
|
||||
const handleBindChange = (e: VisualEditorModel[]) => {
|
||||
console.log(e, 'kkk')
|
||||
}
|
||||
/**
|
||||
* @description 是否处于编辑状态
|
||||
*/
|
||||
const isEdit = computed(() => apis.value.some((item) => item.key == state.ruleForm.key));
|
||||
|
||||
/**
|
||||
* @description 显示添加接口弹窗
|
||||
*/
|
||||
const showModelMoal = () => {
|
||||
const operateType = isEdit.value ? '编辑' : '新增'
|
||||
useModal({
|
||||
title: `${operateType}接口`,
|
||||
props: {
|
||||
width: 600
|
||||
/**
|
||||
* @description 创建空的数据接口对象
|
||||
*/
|
||||
const createEmptyApiItem = (): FetchApiItem => ({
|
||||
key: generateNanoid(),
|
||||
name: '',
|
||||
options: {
|
||||
url: '', // 请求的url
|
||||
method: RequestEnum.GET, // 请求的方法
|
||||
contentType: 'JSON', // 请求的内容类型
|
||||
},
|
||||
content: () => (
|
||||
<ElForm
|
||||
model={state.ruleForm}
|
||||
ref={ruleFormRef}
|
||||
label-width="100px"
|
||||
size={'mini'}
|
||||
rules={rules}
|
||||
>
|
||||
<ElFormItem label="名称" prop="name">
|
||||
<ElInput v-model={state.ruleForm.name} placeholder={'请输入接口名称'}></ElInput>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="接口" prop={'options.url'}>
|
||||
<ElInput v-model={state.ruleForm.options.url} placeholder={'请输入接口地址'}>
|
||||
{{
|
||||
prepend: () => (
|
||||
<ElSelect v-model={state.ruleForm.options.method} class={'w-90px'}>
|
||||
{Object.keys(RequestEnum).map((key) => (
|
||||
<ElOption key={key} label={key} value={key}></ElOption>
|
||||
))}
|
||||
</ElSelect>
|
||||
)
|
||||
}}
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="内容类型" prop={'options.contentType'}>
|
||||
<ElSelect v-model={state.ruleForm.options.contentType}>
|
||||
{Object.keys(ContentTypeEnum).map((key) => (
|
||||
<ElOption key={key} label={key} value={key}></ElOption>
|
||||
))}
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="请求数据" prop={'data.bind'}>
|
||||
<ElCascader
|
||||
v-model={state.ruleForm.data.bind}
|
||||
options={models.value}
|
||||
clearable={true}
|
||||
props={{
|
||||
checkStrictly: true,
|
||||
children: 'entitys',
|
||||
label: 'name',
|
||||
value: 'key',
|
||||
expandTrigger: 'hover'
|
||||
}}
|
||||
placeholder="请选择绑定的请求数据"
|
||||
onChange={handleBindChange}
|
||||
></ElCascader>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="响应数据" prop={'data.recv'}>
|
||||
<ElCascader
|
||||
clearable={true}
|
||||
props={{
|
||||
checkStrictly: true,
|
||||
children: 'entitys',
|
||||
label: 'name',
|
||||
value: 'key',
|
||||
expandTrigger: 'hover'
|
||||
}}
|
||||
placeholder="请选择绑定的响应数据"
|
||||
onChange={handleBindChange}
|
||||
v-model={state.ruleForm.data.recv}
|
||||
options={models.value}
|
||||
></ElCascader>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
),
|
||||
onConfirm: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
ruleFormRef.value?.validate((valid) => {
|
||||
if (valid) {
|
||||
if (isEdit.value) {
|
||||
updateFetchApi(cloneDeep(state.ruleForm))
|
||||
data: {
|
||||
bind: '', // 请求绑定对应的某个实体
|
||||
recv: '', // 响应的结果绑定到某个实体上
|
||||
},
|
||||
});
|
||||
const ruleFormRef = ref<InstanceType<typeof ElForm>>();
|
||||
|
||||
const state = reactive<IState>({
|
||||
activeNames: [],
|
||||
ruleForm: createEmptyApiItem(),
|
||||
});
|
||||
|
||||
const rules = {
|
||||
name: [{ required: true, message: '请输入接口名称', trigger: 'change' }],
|
||||
'options.url': [{ required: true, message: '请输入接口名称', trigger: 'change' }],
|
||||
'options.contentType': [{ required: true, message: '请选择内容类型', trigger: 'change' }],
|
||||
};
|
||||
|
||||
const handleBindChange = (e: VisualEditorModel[]) => {
|
||||
console.log(e, 'kkk');
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 显示添加接口弹窗
|
||||
*/
|
||||
const showModelMoal = () => {
|
||||
const operateType = isEdit.value ? '编辑' : '新增';
|
||||
useModal({
|
||||
title: `${operateType}接口`,
|
||||
props: {
|
||||
width: 600,
|
||||
},
|
||||
content: () => (
|
||||
<ElForm model={state.ruleForm} ref={ruleFormRef} label-width="100px" rules={rules}>
|
||||
<ElFormItem label="名称" prop="name">
|
||||
<ElInput v-model={state.ruleForm.name} placeholder={'请输入接口名称'}></ElInput>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="接口" prop={'options.url'}>
|
||||
<ElInput v-model={state.ruleForm.options.url} placeholder={'请输入接口地址'}>
|
||||
{{
|
||||
prepend: () => (
|
||||
<ElSelect v-model={state.ruleForm.options.method} class={'w-90px'}>
|
||||
{Object.keys(RequestEnum).map((key) => (
|
||||
<ElOption key={key} label={key} value={key}></ElOption>
|
||||
))}
|
||||
</ElSelect>
|
||||
),
|
||||
}}
|
||||
</ElInput>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="内容类型" prop={'options.contentType'}>
|
||||
<ElSelect v-model={state.ruleForm.options.contentType}>
|
||||
{Object.keys(ContentTypeEnum).map((key) => (
|
||||
<ElOption key={key} label={key} value={key}></ElOption>
|
||||
))}
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="请求数据" prop={'data.bind'}>
|
||||
<ElCascader
|
||||
v-model={state.ruleForm.data.bind}
|
||||
options={models.value}
|
||||
clearable={true}
|
||||
props={{
|
||||
checkStrictly: true,
|
||||
children: 'entitys',
|
||||
label: 'name',
|
||||
value: 'key',
|
||||
expandTrigger: 'hover',
|
||||
}}
|
||||
placeholder="请选择绑定的请求数据"
|
||||
onChange={handleBindChange}
|
||||
></ElCascader>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="响应数据" prop={'data.recv'}>
|
||||
<ElCascader
|
||||
clearable={true}
|
||||
props={{
|
||||
checkStrictly: true,
|
||||
children: 'entitys',
|
||||
label: 'name',
|
||||
value: 'key',
|
||||
expandTrigger: 'hover',
|
||||
}}
|
||||
placeholder="请选择绑定的响应数据"
|
||||
onChange={handleBindChange}
|
||||
v-model={state.ruleForm.data.recv}
|
||||
options={models.value}
|
||||
></ElCascader>
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
),
|
||||
onConfirm: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
ruleFormRef.value?.validate((valid) => {
|
||||
if (valid) {
|
||||
if (isEdit.value) {
|
||||
updateFetchApi(cloneDeep(state.ruleForm));
|
||||
} else {
|
||||
incrementFetchApi(cloneDeep(state.ruleForm));
|
||||
}
|
||||
ElMessage.success(`${operateType}接口成功!`);
|
||||
state.ruleForm = createEmptyApiItem();
|
||||
resolve('submit!');
|
||||
} else {
|
||||
incrementFetchApi(cloneDeep(state.ruleForm))
|
||||
reject();
|
||||
console.log('error submit!!');
|
||||
return false;
|
||||
}
|
||||
ElMessage.success(`${operateType}接口成功!`)
|
||||
state.ruleForm = createEmptyApiItem()
|
||||
resolve('submit!')
|
||||
} else {
|
||||
reject()
|
||||
console.log('error submit!!')
|
||||
return false
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
onCancel: () => (state.ruleForm = createEmptyApiItem())
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
onCancel: () => (state.ruleForm = createEmptyApiItem()),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 编辑模型
|
||||
*/
|
||||
const editApiItem = (apiItem: FetchApiItem) => {
|
||||
console.log(apiItem)
|
||||
state.ruleForm = cloneDeep(apiItem)
|
||||
showModelMoal()
|
||||
}
|
||||
/**
|
||||
* @description 编辑模型
|
||||
*/
|
||||
const editApiItem = (apiItem: FetchApiItem) => {
|
||||
console.log(apiItem);
|
||||
state.ruleForm = cloneDeep(apiItem);
|
||||
showModelMoal();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.low-model-item {
|
||||
overflow: auto;
|
||||
.low-model-item {
|
||||
overflow: auto;
|
||||
|
||||
.code {
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
.code {
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.model-item-title {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.model-item-title {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.model-actions {
|
||||
i {
|
||||
padding: 6px;
|
||||
margin: 0 2px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
border-radius: 2px;
|
||||
opacity: 0.7;
|
||||
transition: all 0.1s;
|
||||
.model-actions {
|
||||
display: flex;
|
||||
i {
|
||||
padding: 6px;
|
||||
margin: 0 2px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
border-radius: 2px;
|
||||
opacity: 0.7;
|
||||
transition: all 0.1s;
|
||||
|
||||
&:hover {
|
||||
background-color: #f1f1f1;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.el-icon-delete {
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
&.el-icon-edit {
|
||||
color: #2196f3;
|
||||
&:hover {
|
||||
background-color: #f1f1f1;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -8,13 +8,11 @@
|
|||
-->
|
||||
<template>
|
||||
<div class="!mb-10px">
|
||||
<el-button type="primary" size="small" @click="showModelMoal">添加</el-button>
|
||||
<el-button type="warning" size="small" @click="showImportSwaggerJsonModal"
|
||||
>导入swagger</el-button
|
||||
>
|
||||
<el-button type="primary" @click="showModelMoal">添加</el-button>
|
||||
<el-button type="warning" @click="showImportSwaggerJsonModal">导入swagger</el-button>
|
||||
<el-popconfirm title="确定要删除全部模型吗?" @confirm="updateModel([], true)">
|
||||
<template #reference>
|
||||
<el-button type="danger" size="small">清空</el-button>
|
||||
<el-button type="danger">清空</el-button>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</div>
|
||||
|
@ -25,10 +23,16 @@
|
|||
<div class="model-item-title">
|
||||
<span class="truncate w-160px">{{ item.name }}</span>
|
||||
<div class="model-actions">
|
||||
<i class="el-icon-edit" @click="editModel(item)"></i>
|
||||
<el-popconfirm title="确定要删除该模型吗?" @confirm="deleteModel(item.key)">
|
||||
<el-icon size="24" color="#2196f3" @click.stop="editModel(item)">
|
||||
<Edit />
|
||||
</el-icon>
|
||||
<el-popconfirm
|
||||
title="确定要删除该模型吗?"
|
||||
icon-color="red"
|
||||
@confirm="deleteModel(item.key)"
|
||||
>
|
||||
<template #reference>
|
||||
<i class="el-icon-delete"></i>
|
||||
<el-icon size="24" color="#f44336"><Delete /></el-icon>
|
||||
</template>
|
||||
</el-popconfirm>
|
||||
</div>
|
||||
|
@ -45,238 +49,232 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { reactive, ref, computed } from 'vue'
|
||||
import {
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElSelect,
|
||||
ElOption,
|
||||
ElCard,
|
||||
ElButton,
|
||||
ElMessage
|
||||
} from 'element-plus'
|
||||
import { useVisualData, fieldTypes } from '@/visual-editor/hooks/useVisualData'
|
||||
import type { VisualEditorModel } from '@/visual-editor/visual-editor.utils'
|
||||
import { useModal } from '@/visual-editor/hooks/useModal'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { generateNanoid } from '@/visual-editor/utils/'
|
||||
import { useImportSwaggerJsonModal } from './utils'
|
||||
import { reactive, ref, computed } from 'vue';
|
||||
import {
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
ElInput,
|
||||
ElSelect,
|
||||
ElOption,
|
||||
ElCard,
|
||||
ElButton,
|
||||
ElMessage,
|
||||
} from 'element-plus';
|
||||
import { useVisualData, fieldTypes } from '@/visual-editor/hooks/useVisualData';
|
||||
import type { VisualEditorModel } from '@/visual-editor/visual-editor.utils';
|
||||
import { useModal } from '@/visual-editor/hooks/useModal';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { generateNanoid } from '@/visual-editor/utils/';
|
||||
import { useImportSwaggerJsonModal } from './utils';
|
||||
import { Delete, Edit } from '@element-plus/icons-vue';
|
||||
|
||||
interface IState {
|
||||
activeNames: string[]
|
||||
ruleForm: VisualEditorModel
|
||||
}
|
||||
interface IState {
|
||||
activeNames: string[];
|
||||
ruleForm: VisualEditorModel;
|
||||
}
|
||||
|
||||
const { jsonData, incrementModel, updateModel, deleteModel } = useVisualData()
|
||||
const { jsonData, incrementModel, updateModel, deleteModel } = useVisualData();
|
||||
|
||||
const { showImportSwaggerJsonModal } = useImportSwaggerJsonModal()
|
||||
/**
|
||||
* @description 模型集合
|
||||
*/
|
||||
const models = computed(() => cloneDeep(jsonData.models))
|
||||
const { showImportSwaggerJsonModal } = useImportSwaggerJsonModal();
|
||||
/**
|
||||
* @description 模型集合
|
||||
*/
|
||||
const models = computed(() => cloneDeep(jsonData.models));
|
||||
|
||||
/**
|
||||
* @description 是否处于编辑状态
|
||||
*/
|
||||
const isEdit = computed(() => models.value.some((item) => item.key === state.ruleForm.key))
|
||||
/**
|
||||
* @description 是否处于编辑状态
|
||||
*/
|
||||
const isEdit = computed(() => models.value.some((item) => item.key === state.ruleForm.key));
|
||||
|
||||
/**
|
||||
* @description 创建空的实体对象
|
||||
*/
|
||||
const createEmptyEntity = () => ({ key: '', name: '', type: 'string', value: '' })
|
||||
/**
|
||||
* @description 创建空的实体对象
|
||||
*/
|
||||
const createEmptyEntity = () => ({ key: '', name: '', type: 'string', value: '' });
|
||||
|
||||
/**
|
||||
* @description 创建空的数据模型
|
||||
*/
|
||||
const createEmptyModel = () => ({
|
||||
name: '',
|
||||
key: generateNanoid(),
|
||||
entitys: [createEmptyEntity()]
|
||||
})
|
||||
/**
|
||||
* @description 创建空的数据模型
|
||||
*/
|
||||
const createEmptyModel = () => ({
|
||||
name: '',
|
||||
key: generateNanoid(),
|
||||
entitys: [createEmptyEntity()],
|
||||
});
|
||||
|
||||
const ruleFormRef = ref<InstanceType<typeof ElForm>>()
|
||||
const ruleFormRef = ref<InstanceType<typeof ElForm>>();
|
||||
|
||||
const state = reactive<IState>({
|
||||
activeNames: [],
|
||||
ruleForm: createEmptyModel()
|
||||
})
|
||||
const state = reactive<IState>({
|
||||
activeNames: [],
|
||||
ruleForm: createEmptyModel(),
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {number} 索引
|
||||
* @description 删除实体项
|
||||
*/
|
||||
const deleteEntityItem = (index: number) => {
|
||||
state.ruleForm.entitys.splice(index, 1)
|
||||
}
|
||||
/**
|
||||
* @param {number} 索引
|
||||
* @description 删除实体项
|
||||
*/
|
||||
const deleteEntityItem = (index: number) => {
|
||||
state.ruleForm.entitys.splice(index, 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 添加实体项
|
||||
*/
|
||||
const addEntityItem = () => {
|
||||
state.ruleForm.entitys.push(createEmptyEntity())
|
||||
}
|
||||
/**
|
||||
* @description 添加实体项
|
||||
*/
|
||||
const addEntityItem = () => {
|
||||
state.ruleForm.entitys.push(createEmptyEntity());
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 显示添加接口弹窗
|
||||
*/
|
||||
const showModelMoal = () => {
|
||||
const operateType = isEdit.value ? '修改' : '新增'
|
||||
useModal({
|
||||
title: `${operateType}数据源`,
|
||||
props: {
|
||||
width: 600
|
||||
},
|
||||
content: () => (
|
||||
<ElForm model={state.ruleForm} ref={ruleFormRef} label-width="100px" size={'mini'}>
|
||||
<ElFormItem
|
||||
label="数据源名称"
|
||||
prop="name"
|
||||
rules={[{ required: true, message: '请输入数据源名称', trigger: 'change' }]}
|
||||
>
|
||||
<ElInput v-model={state.ruleForm.name} placeholder={'请输入数据源名称'}></ElInput>
|
||||
</ElFormItem>
|
||||
{!state.ruleForm.entitys.length && (
|
||||
<ElFormItem>
|
||||
<ElButton onClick={addEntityItem} type={'primary'} size={'mini'}>
|
||||
添加实体
|
||||
</ElButton>
|
||||
</ElFormItem>
|
||||
)}
|
||||
{state.ruleForm.entitys.map((entity, index) => (
|
||||
<ElCard
|
||||
key={index}
|
||||
shadow={'hover'}
|
||||
class={'mt-10px'}
|
||||
v-slots={{
|
||||
header: () => (
|
||||
<div class={'flex justify-between'}>
|
||||
<ElFormItem
|
||||
label="实体名称"
|
||||
prop={`entitys.${index}.name`}
|
||||
rules={[{ required: true, message: '请输入实体名称', trigger: 'change' }]}
|
||||
showMessage={false}
|
||||
class={'w-300px !mb-0'}
|
||||
>
|
||||
<ElInput v-model={entity.name} placeholder={'请输入实体名称'}></ElInput>
|
||||
</ElFormItem>
|
||||
<div>
|
||||
<ElButton onClick={() => deleteEntityItem(index)} type={'danger'} size={'mini'}>
|
||||
删除
|
||||
</ElButton>
|
||||
<ElButton onClick={addEntityItem} type={'primary'} size={'mini'}>
|
||||
添加
|
||||
</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}}
|
||||
/**
|
||||
* @description 显示添加接口弹窗
|
||||
*/
|
||||
const showModelMoal = () => {
|
||||
const operateType = isEdit.value ? '修改' : '新增';
|
||||
useModal({
|
||||
title: `${operateType}数据源`,
|
||||
props: {
|
||||
width: 600,
|
||||
},
|
||||
content: () => (
|
||||
<ElForm model={state.ruleForm} ref={ruleFormRef} label-width="100px">
|
||||
<ElFormItem
|
||||
label="数据源名称"
|
||||
prop="name"
|
||||
rules={[{ required: true, message: '请输入数据源名称', trigger: 'change' }]}
|
||||
>
|
||||
<ElFormItem
|
||||
label="实体字段"
|
||||
prop={`entitys.${index}.key`}
|
||||
rules={[{ required: true, message: '请输入实体字段', trigger: 'change' }]}
|
||||
<ElInput v-model={state.ruleForm.name} placeholder={'请输入数据源名称'}></ElInput>
|
||||
</ElFormItem>
|
||||
{!state.ruleForm.entitys.length && (
|
||||
<ElFormItem>
|
||||
<ElButton onClick={addEntityItem} type={'primary'}>
|
||||
添加实体
|
||||
</ElButton>
|
||||
</ElFormItem>
|
||||
)}
|
||||
{state.ruleForm.entitys.map((entity, index) => (
|
||||
<ElCard
|
||||
key={index}
|
||||
shadow={'hover'}
|
||||
class={'mt-10px'}
|
||||
v-slots={{
|
||||
header: () => (
|
||||
<div class={'flex justify-between'}>
|
||||
<ElFormItem
|
||||
label="实体名称"
|
||||
prop={`entitys.${index}.name`}
|
||||
rules={[{ required: true, message: '请输入实体名称', trigger: 'change' }]}
|
||||
showMessage={false}
|
||||
class={'w-300px !mb-0'}
|
||||
>
|
||||
<ElInput v-model={entity.name} placeholder={'请输入实体名称'}></ElInput>
|
||||
</ElFormItem>
|
||||
<div>
|
||||
<ElButton onClick={() => deleteEntityItem(index)} type={'danger'}>
|
||||
删除
|
||||
</ElButton>
|
||||
<ElButton onClick={addEntityItem} type={'primary'}>
|
||||
添加
|
||||
</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
),
|
||||
}}
|
||||
>
|
||||
<ElInput v-model={entity.key} placeholder={'请输入实体字段'}></ElInput>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
label="数据类型"
|
||||
prop={`entitys.${index}.type`}
|
||||
rules={[{ required: true, message: '请输入数据类型', trigger: 'change' }]}
|
||||
>
|
||||
<ElSelect v-model={entity.type}>
|
||||
{fieldTypes.map((typeItem) => (
|
||||
<ElOption
|
||||
key={typeItem.value}
|
||||
label={typeItem.label}
|
||||
value={typeItem.value}
|
||||
></ElOption>
|
||||
))}
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="默认数据" prop={`entitys.${index}.value`}>
|
||||
<ElInput
|
||||
v-model={entity.value}
|
||||
placeholder={'实体默认数据,不填则为对应类型数据'}
|
||||
></ElInput>
|
||||
</ElFormItem>
|
||||
</ElCard>
|
||||
))}
|
||||
</ElForm>
|
||||
),
|
||||
onConfirm: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
ruleFormRef.value?.validate((valid) => {
|
||||
if (valid) {
|
||||
if (isEdit.value) {
|
||||
updateModel(state.ruleForm)
|
||||
<ElFormItem
|
||||
label="实体字段"
|
||||
prop={`entitys.${index}.key`}
|
||||
rules={[{ required: true, message: '请输入实体字段', trigger: 'change' }]}
|
||||
>
|
||||
<ElInput v-model={entity.key} placeholder={'请输入实体字段'}></ElInput>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
label="数据类型"
|
||||
prop={`entitys.${index}.type`}
|
||||
rules={[{ required: true, message: '请输入数据类型', trigger: 'change' }]}
|
||||
>
|
||||
<ElSelect v-model={entity.type}>
|
||||
{fieldTypes.map((typeItem) => (
|
||||
<ElOption
|
||||
key={typeItem.value}
|
||||
label={typeItem.label}
|
||||
value={typeItem.value}
|
||||
></ElOption>
|
||||
))}
|
||||
</ElSelect>
|
||||
</ElFormItem>
|
||||
<ElFormItem label="默认数据" prop={`entitys.${index}.value`}>
|
||||
<ElInput
|
||||
v-model={entity.value}
|
||||
placeholder={'实体默认数据,不填则为对应类型数据'}
|
||||
></ElInput>
|
||||
</ElFormItem>
|
||||
</ElCard>
|
||||
))}
|
||||
</ElForm>
|
||||
),
|
||||
onConfirm: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
ruleFormRef.value?.validate((valid) => {
|
||||
if (valid) {
|
||||
if (isEdit.value) {
|
||||
updateModel(state.ruleForm);
|
||||
} else {
|
||||
incrementModel(state.ruleForm);
|
||||
}
|
||||
ElMessage.success(`${operateType}模型成功!`);
|
||||
state.ruleForm = createEmptyModel();
|
||||
resolve('submit!');
|
||||
} else {
|
||||
incrementModel(state.ruleForm)
|
||||
reject();
|
||||
console.log('error submit!!');
|
||||
return false;
|
||||
}
|
||||
ElMessage.success(`${operateType}模型成功!`)
|
||||
state.ruleForm = createEmptyModel()
|
||||
resolve('submit!')
|
||||
} else {
|
||||
reject()
|
||||
console.log('error submit!!')
|
||||
return false
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
onCancel: () => (state.ruleForm = createEmptyModel())
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @description 编辑模型
|
||||
*/
|
||||
const editModel = (model: VisualEditorModel) => {
|
||||
console.log(model)
|
||||
state.ruleForm = cloneDeep(model)
|
||||
showModelMoal()
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
onCancel: () => (state.ruleForm = createEmptyModel()),
|
||||
});
|
||||
};
|
||||
/**
|
||||
* @description 编辑模型
|
||||
*/
|
||||
const editModel = (model: VisualEditorModel) => {
|
||||
console.log(model);
|
||||
state.ruleForm = cloneDeep(model);
|
||||
showModelMoal();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.low-model-item {
|
||||
overflow: auto;
|
||||
.low-model-item {
|
||||
overflow: auto;
|
||||
|
||||
.code {
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
.code {
|
||||
padding: 4px 10px;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.model-item-title {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
.model-item-title {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.model-actions {
|
||||
i {
|
||||
padding: 6px;
|
||||
margin: 0 2px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
border-radius: 2px;
|
||||
opacity: 0.7;
|
||||
transition: all 0.1s;
|
||||
.model-actions {
|
||||
display: flex;
|
||||
i {
|
||||
padding: 6px;
|
||||
margin: 0 2px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
border-radius: 2px;
|
||||
opacity: 0.7;
|
||||
transition: all 0.1s;
|
||||
|
||||
&:hover {
|
||||
background-color: #f1f1f1;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.el-icon-delete {
|
||||
color: #f44336;
|
||||
}
|
||||
|
||||
&.el-icon-edit {
|
||||
color: #2196f3;
|
||||
&:hover {
|
||||
background-color: #f1f1f1;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -18,28 +18,30 @@
|
|||
</template>
|
||||
|
||||
<script lang="tsx">
|
||||
export default {
|
||||
label: '数据源',
|
||||
order: 2,
|
||||
icon: 'el-icon-data-board'
|
||||
}
|
||||
import { DataBoard } from '@element-plus/icons-vue';
|
||||
|
||||
export default {
|
||||
label: '数据源',
|
||||
order: 2,
|
||||
icon: DataBoard,
|
||||
};
|
||||
</script>
|
||||
<script setup lang="tsx" name="基本组件">
|
||||
import DataModel from './data-model.vue'
|
||||
import DataFetch from './data-fetch.vue'
|
||||
import DataModel from './data-model.vue';
|
||||
import DataFetch from './data-fetch.vue';
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.data-source :deep {
|
||||
.el-tabs__header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
.data-source :deep {
|
||||
.el-tabs__header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.el-tabs__content {
|
||||
contain: layout;
|
||||
content-visibility: auto;
|
||||
.el-tabs__content {
|
||||
contain: layout;
|
||||
content-visibility: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -6,87 +6,87 @@
|
|||
* @Description:
|
||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\left-aside\components\data-source\utils.tsx
|
||||
*/
|
||||
import { generateNanoid } from '@/visual-editor/utils'
|
||||
import type { FetchApiItem } from '@/visual-editor/visual-editor.utils'
|
||||
import { RequestEnum } from '@/enums/httpEnum'
|
||||
import MonacoEditor from '@/visual-editor/components/common/monaco-editor/MonacoEditor'
|
||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData'
|
||||
import type { VisualEditorModel } from '@/visual-editor/visual-editor.utils'
|
||||
import { useModal } from '@/visual-editor/hooks/useModal'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { generateNanoid } from '@/visual-editor/utils';
|
||||
import type { FetchApiItem } from '@/visual-editor/visual-editor.utils';
|
||||
import { RequestEnum } from '@/enums/httpEnum';
|
||||
import MonacoEditor from '@/visual-editor/components/common/monaco-editor/MonacoEditor';
|
||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData';
|
||||
import type { VisualEditorModel } from '@/visual-editor/visual-editor.utils';
|
||||
import { useModal } from '@/visual-editor/hooks/useModal';
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
/**
|
||||
* @description 导入丝袜哥
|
||||
* @description 导入丝袜哥, eg: 简单的解析代码,需要根据自己需要完善
|
||||
* @param {object} swagger 丝袜哥JSON字符串
|
||||
* @returns { apis, models }
|
||||
*/
|
||||
export const importSwaggerJson = (swagger: any) => {
|
||||
swagger = typeof swagger == 'string' ? JSON.parse(swagger) : swagger
|
||||
const models: VisualEditorModel[] = []
|
||||
swagger = typeof swagger == 'string' ? JSON.parse(swagger) : swagger;
|
||||
const models: VisualEditorModel[] = [];
|
||||
Object.keys(swagger.definitions).forEach((model) => {
|
||||
const properties = swagger.definitions[model].properties
|
||||
const properties = swagger.definitions[model].properties;
|
||||
const modelItem: VisualEditorModel = {
|
||||
name: model,
|
||||
key: generateNanoid(),
|
||||
entitys: []
|
||||
}
|
||||
entitys: [],
|
||||
};
|
||||
Object.keys(properties).forEach((field) => {
|
||||
modelItem.entitys.push({
|
||||
key: field,
|
||||
name: properties[field].description || field,
|
||||
type: properties[field].type,
|
||||
value: ''
|
||||
})
|
||||
})
|
||||
models.push(modelItem)
|
||||
})
|
||||
const apis: FetchApiItem[] = []
|
||||
value: '',
|
||||
});
|
||||
});
|
||||
models.push(modelItem);
|
||||
});
|
||||
const apis: FetchApiItem[] = [];
|
||||
Object.keys(swagger.paths).forEach((url) => {
|
||||
Object.keys(swagger.paths[url]).forEach((method) => {
|
||||
const apiUrlObj = swagger.paths[url][method]
|
||||
const model = apiUrlObj.parameters?.[0]?.schema?.$ref?.split('/').pop()
|
||||
const bindTarget = model ? models.find((item) => item.name == model) : undefined
|
||||
typeof bindTarget == 'object' && (bindTarget.name = apiUrlObj.summary)
|
||||
const apiUrlObj = swagger.paths[url][method];
|
||||
const model = apiUrlObj.parameters?.[0]?.schema?.$ref?.split('/').pop();
|
||||
const bindTarget = model ? models.find((item) => item.name == model) : undefined;
|
||||
typeof bindTarget == 'object' && (bindTarget.name = apiUrlObj.summary);
|
||||
const api: FetchApiItem = {
|
||||
key: generateNanoid(),
|
||||
name: apiUrlObj.summary,
|
||||
options: {
|
||||
url: url, // 请求的url
|
||||
method: method.toLocaleUpperCase() as RequestEnum, // 请求的方法
|
||||
contentType: apiUrlObj.produces[0] || apiUrlObj.consumes[0] // 请求的内容类型
|
||||
contentType: apiUrlObj.produces[0] || apiUrlObj.consumes[0], // 请求的内容类型
|
||||
},
|
||||
data: {
|
||||
bind: bindTarget?.key || '', // 请求绑定对应的某个实体
|
||||
recv: '' // 响应的结果绑定到某个实体上
|
||||
}
|
||||
}
|
||||
apis.push(api)
|
||||
})
|
||||
})
|
||||
return { apis, models }
|
||||
}
|
||||
recv: '', // 响应的结果绑定到某个实体上
|
||||
},
|
||||
};
|
||||
apis.push(api);
|
||||
});
|
||||
});
|
||||
return { apis, models };
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 显示导入swagger JSON模态框
|
||||
*/
|
||||
export const useImportSwaggerJsonModal = () => {
|
||||
const { updateModel, updateFetchApi } = useVisualData()
|
||||
const { updateModel, updateFetchApi } = useVisualData();
|
||||
|
||||
const shema = {}
|
||||
const shema = {};
|
||||
const handleSchemaChange = (val) => {
|
||||
try {
|
||||
const newObj = JSON.parse(val)
|
||||
Object.assign(shema, newObj)
|
||||
const newObj = JSON.parse(val);
|
||||
Object.assign(shema, newObj);
|
||||
} catch (e) {
|
||||
console.log('JSON格式有误:', e)
|
||||
console.log('JSON格式有误:', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
return {
|
||||
showImportSwaggerJsonModal: () =>
|
||||
useModal({
|
||||
title: '导入swagger JSON (支持swagger: 2.0)',
|
||||
props: {
|
||||
width: 760
|
||||
width: 760,
|
||||
},
|
||||
content: () => (
|
||||
<MonacoEditor
|
||||
|
@ -99,15 +99,15 @@ export const useImportSwaggerJsonModal = () => {
|
|||
),
|
||||
onConfirm: () => {
|
||||
try {
|
||||
const { models, apis } = importSwaggerJson(shema)
|
||||
updateModel(models, true)
|
||||
updateFetchApi(apis, true)
|
||||
ElMessage.success('导入成功!')
|
||||
console.log({ models, apis }, '导入的swagger')
|
||||
const { models, apis } = importSwaggerJson(shema);
|
||||
updateModel(models, true);
|
||||
updateFetchApi(apis, true);
|
||||
ElMessage.success('导入成功!');
|
||||
console.log({ models, apis }, '导入的swagger');
|
||||
} catch (e) {
|
||||
ElMessage.success('导入失败!请检查swagger JSON是否有误!')
|
||||
ElMessage.success('导入失败!请检查swagger JSON是否有误!');
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
<!--页面树-->
|
||||
<template>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="small"
|
||||
class="!my-10px !mx-6px"
|
||||
icon="el-icon-plus"
|
||||
@click="addPage"
|
||||
<el-button type="primary" class="!my-10px !mx-6px" :icon="Plus" @click="addPage"
|
||||
>添加页面</el-button
|
||||
>
|
||||
<el-tree
|
||||
|
@ -21,23 +16,19 @@
|
|||
<span
|
||||
>{{ node.label }}({{ data.path }})
|
||||
<template v-if="data.isDefault">
|
||||
<el-tag size="mini">默认</el-tag>
|
||||
<el-tag size="default">默认</el-tag>
|
||||
</template>
|
||||
</span>
|
||||
<span @click.stop>
|
||||
<el-dropdown trigger="click">
|
||||
<span class="el-dropdown-link">
|
||||
<i class="el-icon-more"></i>
|
||||
<el-icon><more-filled /></el-icon>
|
||||
</span>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item icon="el-icon-edit" @click="editPage(data)"
|
||||
>编辑</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item icon="el-icon-delete" @click="delPage(data)"
|
||||
>删除</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item icon="el-icon-link" @click="setDefaultPage(data)"
|
||||
<el-dropdown-item :icon="Edit" @click="editPage(data)">编辑</el-dropdown-item>
|
||||
<el-dropdown-item :icon="Delete" @click="delPage(data)">删除</el-dropdown-item>
|
||||
<el-dropdown-item :icon="Link" @click="setDefaultPage(data)"
|
||||
>设为首页</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
|
@ -50,157 +41,148 @@
|
|||
</template>
|
||||
|
||||
<script lang="tsx">
|
||||
import { defineComponent, reactive, ref, computed, toRefs } from 'vue'
|
||||
import { useVisualData, createNewPage } from '@/visual-editor/hooks/useVisualData'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { ElMessage, ElForm, ElFormItem, ElInput } from 'element-plus'
|
||||
import { useModal } from '@/visual-editor/hooks/useModal'
|
||||
export default {
|
||||
name: 'PageTree',
|
||||
label: '页面',
|
||||
order: 1,
|
||||
icon: Tickets,
|
||||
};
|
||||
</script>
|
||||
|
||||
const rules = {
|
||||
title: [{ required: true, message: '请输入页面标题', trigger: 'blur' }],
|
||||
path: [{ required: true, message: '请输入页面路径', trigger: 'blur' }]
|
||||
}
|
||||
<script lang="tsx" setup>
|
||||
import { ref, computed } from 'vue';
|
||||
import { useVisualData, createNewPage } from '@/visual-editor/hooks/useVisualData';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { ElMessage, ElForm, ElFormItem, ElInput } from 'element-plus';
|
||||
import { useModal } from '@/visual-editor/hooks/useModal';
|
||||
import type { VisualEditorPage } from '@/visual-editor/visual-editor.utils';
|
||||
import { Tickets, Plus, MoreFilled, Edit, Delete, Link } from '@element-plus/icons-vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'PageTree',
|
||||
label: '页面',
|
||||
order: 1,
|
||||
icon: 'el-icon-tickets',
|
||||
setup() {
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const rules = {
|
||||
title: [{ required: true, message: '请输入页面标题', trigger: 'blur' }],
|
||||
path: [{ required: true, message: '请输入页面路径', trigger: 'blur' }],
|
||||
};
|
||||
|
||||
const { jsonData, setCurrentPage, deletePage, updatePage, incrementPage } = useVisualData()
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
|
||||
const ruleFormRef = ref<InstanceType<typeof ElForm>>()
|
||||
const { jsonData, setCurrentPage, deletePage, updatePage, incrementPage } = useVisualData();
|
||||
|
||||
const state = reactive({
|
||||
defaultProps: {
|
||||
children: 'children',
|
||||
label: 'title'
|
||||
const ruleFormRef = ref<InstanceType<typeof ElForm>>();
|
||||
|
||||
const defaultProps = ref({
|
||||
children: 'children',
|
||||
label: 'title',
|
||||
});
|
||||
const currentNodeKey = ref(route.path);
|
||||
// 当前要增加或修改的页面
|
||||
const operatePageData = ref<VisualEditorPage | null>(null);
|
||||
// 增改页面表单数据
|
||||
const form = ref({
|
||||
title: '',
|
||||
path: '',
|
||||
});
|
||||
|
||||
// 所有的页面
|
||||
const pages = computed(() =>
|
||||
Object.keys(jsonData.pages).map((key) => ({
|
||||
title: jsonData.pages[key].title,
|
||||
path: key,
|
||||
})),
|
||||
);
|
||||
|
||||
// 点击当前节点
|
||||
const handleNodeClick = (data) => {
|
||||
setCurrentPage(data.path);
|
||||
router.push(data.path);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 显示新增/编辑模态框
|
||||
*/
|
||||
const showOparateModal = () =>
|
||||
useModal({
|
||||
title: operatePageData.value ? '编辑页面' : '新增页面',
|
||||
props: {
|
||||
width: 380,
|
||||
},
|
||||
currentNodeKey: route.path,
|
||||
operatePageData: null as any, // 当前要增加或修改的页面
|
||||
form: {
|
||||
// 增改页面表单数据
|
||||
title: '',
|
||||
path: ''
|
||||
}
|
||||
})
|
||||
|
||||
// 所有的页面
|
||||
const pages = computed(() =>
|
||||
Object.keys(jsonData.pages).map((key) => ({
|
||||
title: jsonData.pages[key].title,
|
||||
path: key
|
||||
}))
|
||||
)
|
||||
|
||||
// 点击当前节点
|
||||
const handleNodeClick = (data) => {
|
||||
setCurrentPage(data.path)
|
||||
router.push(data.path)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 显示新增/编辑模态框
|
||||
*/
|
||||
const showOparateModal = () =>
|
||||
useModal({
|
||||
title: state.operatePageData ? '编辑页面' : '新增页面',
|
||||
props: {
|
||||
width: 380
|
||||
},
|
||||
content: () => (
|
||||
<ElForm ref={ruleFormRef} model={state.form} rules={rules}>
|
||||
<ElFormItem prop={'title'} label={'页面标题'} labelWidth={'80px'}>
|
||||
<ElInput v-model={state.form.title} />
|
||||
</ElFormItem>
|
||||
<ElFormItem prop={'path'} label={'页面路径'} labelWidth={'80px'}>
|
||||
<ElInput v-model={state.form.path} />
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
),
|
||||
onConfirm: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
ruleFormRef.value?.validate(async (valid) => {
|
||||
if (valid) {
|
||||
const { title, path } = state.form
|
||||
if ([title.trim(), path.trim()].includes('')) {
|
||||
return ElMessage.error('标题或路径不能为空!')
|
||||
}
|
||||
if (state.operatePageData) {
|
||||
updatePage({
|
||||
newPath: path,
|
||||
oldPath: state.operatePageData.path || path,
|
||||
page: { title }
|
||||
})
|
||||
await router.replace(path)
|
||||
state.currentNodeKey = path
|
||||
} else {
|
||||
incrementPage(path, createNewPage({ title }))
|
||||
}
|
||||
resolve(true)
|
||||
} else {
|
||||
console.log('error submit!!')
|
||||
reject()
|
||||
return false
|
||||
content: () => (
|
||||
<ElForm ref={ruleFormRef} model={form.value} rules={rules}>
|
||||
<ElFormItem prop={'title'} label={'页面标题'} labelWidth={'80px'}>
|
||||
<ElInput v-model={form.value.title} />
|
||||
</ElFormItem>
|
||||
<ElFormItem prop={'path'} label={'页面路径'} labelWidth={'80px'}>
|
||||
<ElInput v-model={form.value.path} />
|
||||
</ElFormItem>
|
||||
</ElForm>
|
||||
),
|
||||
onConfirm: () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
ruleFormRef.value?.validate(async (valid) => {
|
||||
if (valid) {
|
||||
const { title, path } = form.value;
|
||||
if ([title.trim(), path.trim()].includes('')) {
|
||||
return ElMessage.error('标题或路径不能为空!');
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
if (operatePageData.value) {
|
||||
updatePage({
|
||||
newPath: path,
|
||||
oldPath: operatePageData.value.path || path,
|
||||
page: { title },
|
||||
});
|
||||
await router.replace(path);
|
||||
currentNodeKey.value = path;
|
||||
} else {
|
||||
incrementPage(path, createNewPage({ title }));
|
||||
}
|
||||
resolve(true);
|
||||
} else {
|
||||
console.log('error submit!!');
|
||||
reject();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// 新增页面
|
||||
const addPage = () => {
|
||||
state.operatePageData = null
|
||||
state.form = {
|
||||
title: '',
|
||||
path: ''
|
||||
}
|
||||
showOparateModal()
|
||||
}
|
||||
// 编辑页面
|
||||
const editPage = (data) => {
|
||||
state.operatePageData = data
|
||||
state.form = {
|
||||
title: data.title,
|
||||
path: data.path
|
||||
}
|
||||
showOparateModal()
|
||||
console.log('子页面数据:', data)
|
||||
}
|
||||
// 删除子页面
|
||||
const delPage = (data) => {
|
||||
console.log('删除子页面数据', data)
|
||||
deletePage(data.path, '/')
|
||||
}
|
||||
// 设置为默认页面
|
||||
const setDefaultPage = (data) => {
|
||||
console.log('设置该页面为默认页面', data)
|
||||
}
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
pages,
|
||||
setCurrentPage,
|
||||
setDefaultPage,
|
||||
handleNodeClick,
|
||||
addPage,
|
||||
editPage,
|
||||
delPage
|
||||
}
|
||||
}
|
||||
})
|
||||
// 新增页面
|
||||
const addPage = () => {
|
||||
operatePageData.value = null;
|
||||
form.value = {
|
||||
title: '',
|
||||
path: '',
|
||||
};
|
||||
showOparateModal();
|
||||
};
|
||||
// 编辑页面
|
||||
const editPage = (data) => {
|
||||
operatePageData.value = data;
|
||||
form.value = {
|
||||
title: data.title,
|
||||
path: data.path,
|
||||
};
|
||||
showOparateModal();
|
||||
console.log('子页面数据:', data);
|
||||
};
|
||||
// 删除子页面
|
||||
const delPage = (data) => {
|
||||
console.log('删除子页面数据', data);
|
||||
deletePage(data.path, '/');
|
||||
};
|
||||
// 设置为默认页面
|
||||
const setDefaultPage = (data) => {
|
||||
console.log('设置该页面为默认页面', data);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.custom-tree-node {
|
||||
display: flex;
|
||||
padding-right: 8px;
|
||||
font-size: 14px;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.custom-tree-node {
|
||||
display: flex;
|
||||
padding-right: 8px;
|
||||
font-size: 14px;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -12,71 +12,64 @@
|
|||
<el-tab-pane :name="tabItem.name" lazy>
|
||||
<template #label>
|
||||
<div class="tab-item">
|
||||
<i :class="tabItem.icon"></i>
|
||||
<el-icon :size="26"><component :is="tabItem.icon" /></el-icon>
|
||||
{{ tabItem.label }}
|
||||
</div>
|
||||
</template>
|
||||
<component :is="tabItem.name" v-bind="$attrs" />
|
||||
<component :is="tabItem.comp" v-bind="$attrs" />
|
||||
</el-tab-pane>
|
||||
</template>
|
||||
</el-tabs>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
/**
|
||||
* @description 左侧边栏
|
||||
*/
|
||||
import { defineComponent, reactive, toRefs } from 'vue'
|
||||
import components from './components'
|
||||
export default {
|
||||
name: 'LeftAside',
|
||||
};
|
||||
</script>
|
||||
|
||||
const tabs = Object.keys(components)
|
||||
.map((name) => {
|
||||
const { label, icon, order } = components[name]
|
||||
return { label, icon, name, order }
|
||||
})
|
||||
.sort((a, b) => a.order - b.order)
|
||||
<script lang="ts" setup>
|
||||
/**
|
||||
* @description 左侧边栏
|
||||
*/
|
||||
import { ref } from 'vue';
|
||||
import components from './components';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'LeftAside',
|
||||
components,
|
||||
setup() {
|
||||
const state = reactive({
|
||||
activeName: tabs[0].name
|
||||
const tabs = Object.keys(components)
|
||||
.map((name) => {
|
||||
const { label, icon, order } = components[name];
|
||||
return { label, icon, name, order, comp: components[name] };
|
||||
})
|
||||
.sort((a, b) => a.order - b.order);
|
||||
|
||||
return {
|
||||
...toRefs(state),
|
||||
tabs
|
||||
}
|
||||
}
|
||||
})
|
||||
const activeName = ref(tabs[0].name);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.left-aside {
|
||||
height: 100%;
|
||||
contain: layout;
|
||||
.left-aside {
|
||||
height: 100%;
|
||||
contain: layout;
|
||||
|
||||
> :deep(.el-tabs__header) {
|
||||
margin-right: 0;
|
||||
> :deep(.el-tabs__header) {
|
||||
margin-right: 0;
|
||||
|
||||
.el-tabs__item {
|
||||
height: 80px;
|
||||
padding: 20px 16px;
|
||||
.el-tabs__item {
|
||||
height: 80px;
|
||||
padding: 20px 16px;
|
||||
|
||||
.tab-item {
|
||||
@apply flex flex-col items-center justify-center;
|
||||
.tab-item {
|
||||
@apply flex flex-col items-center justify-center;
|
||||
|
||||
[class^='el-icon-'] {
|
||||
font-size: 20px;
|
||||
[class^='el-icon-'] {
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> :deep(.el-tabs__content) {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
> :deep(.el-tabs__content) {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -6,38 +6,40 @@
|
|||
* @Description: 动画组件
|
||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\animate\Animate.tsx
|
||||
*/
|
||||
import { defineComponent, reactive, ref, watchEffect } from 'vue'
|
||||
import { ElTabs, ElTabPane, ElRow, ElCol, ElButton, ElSwitch, ElAlert } from 'element-plus'
|
||||
import { animationTabs } from './animateConfig'
|
||||
import styles from './animate.module.scss'
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData'
|
||||
import type { Animation } from '@/visual-editor/visual-editor.utils'
|
||||
import { useAnimate } from '@/hooks/useAnimate'
|
||||
import { defineComponent, reactive, ref, watchEffect } from 'vue';
|
||||
import { ElTabs, ElTabPane, ElRow, ElCol, ElButton, ElSwitch, ElAlert, ElIcon } from 'element-plus';
|
||||
import { animationTabs } from './animateConfig';
|
||||
import styles from './animate.module.scss';
|
||||
import { onClickOutside } from '@vueuse/core';
|
||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData';
|
||||
import type { Animation } from '@/visual-editor/visual-editor.utils';
|
||||
import { useAnimate } from '@/hooks/useAnimate';
|
||||
import { Plus, CaretRight } from '@element-plus/icons-vue';
|
||||
import 'element-plus/es/components/alert/style/css';
|
||||
|
||||
export const Animate = defineComponent({
|
||||
setup() {
|
||||
const { currentBlock } = useVisualData()
|
||||
const target = ref<InstanceType<typeof HTMLDivElement>>()
|
||||
const { currentBlock } = useVisualData();
|
||||
const target = ref<InstanceType<typeof HTMLDivElement>>();
|
||||
|
||||
const state = reactive({
|
||||
activeName: '',
|
||||
isAddAnimates: false, // 是否显示添加动画集
|
||||
changeTargetIndex: -1 // 要修改的动画的索引
|
||||
})
|
||||
changeTargetIndex: -1, // 要修改的动画的索引
|
||||
});
|
||||
|
||||
onClickOutside(target, () => (state.isAddAnimates = false))
|
||||
onClickOutside(target, () => (state.isAddAnimates = false));
|
||||
|
||||
watchEffect((onInvalidate) => {
|
||||
if (state.isAddAnimates) {
|
||||
state.activeName = 'in'
|
||||
state.activeName = 'in';
|
||||
} else {
|
||||
state.changeTargetIndex = -1
|
||||
state.changeTargetIndex = -1;
|
||||
}
|
||||
onInvalidate(() => {
|
||||
console.log('onInvalidate')
|
||||
})
|
||||
})
|
||||
console.log('onInvalidate');
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 运行动画
|
||||
|
@ -45,47 +47,47 @@ export const Animate = defineComponent({
|
|||
const runAnimation = (animation: Animation | Animation[] = []) => {
|
||||
let animateEl =
|
||||
(window.$$refs[currentBlock.value._vid]?.$el as HTMLElement) ??
|
||||
(window.$$refs[currentBlock.value._vid] as HTMLElement)
|
||||
(window.$$refs[currentBlock.value._vid] as HTMLElement);
|
||||
|
||||
animateEl = animateEl?.closest('.list-group-item')?.firstChild as HTMLElement
|
||||
animateEl = animateEl?.closest('.list-group-item')?.firstChild as HTMLElement;
|
||||
|
||||
if (animateEl) {
|
||||
useAnimate(animateEl, animation)
|
||||
useAnimate(animateEl, animation);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 点击要修改的动画名称
|
||||
*/
|
||||
const clickAnimateName = (index) => {
|
||||
state.changeTargetIndex = index
|
||||
state.isAddAnimates = true
|
||||
}
|
||||
state.changeTargetIndex = index;
|
||||
state.isAddAnimates = true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 删除动画
|
||||
* @param index 要删除的动画的索引
|
||||
* @returns
|
||||
*/
|
||||
const delAnimate = (index: number) => currentBlock.value.animations?.splice(index, 1)
|
||||
const delAnimate = (index: number) => currentBlock.value.animations?.splice(index, 1);
|
||||
|
||||
/**
|
||||
* @description 添加/修改 动画
|
||||
*/
|
||||
const addOrChangeAnimate = (animateItem: Animation) => {
|
||||
const animation: Animation = {
|
||||
...animateItem
|
||||
}
|
||||
...animateItem,
|
||||
};
|
||||
if (state.changeTargetIndex == -1) {
|
||||
currentBlock.value.animations?.push(animation)
|
||||
currentBlock.value.animations?.push(animation);
|
||||
} else {
|
||||
// 修改动画
|
||||
currentBlock.value.animations![state.changeTargetIndex] = animation
|
||||
state.changeTargetIndex = -1
|
||||
currentBlock.value.animations![state.changeTargetIndex] = animation;
|
||||
state.changeTargetIndex = -1;
|
||||
}
|
||||
state.isAddAnimates = false
|
||||
console.log(currentBlock.value.animations, '当前组件的动画')
|
||||
}
|
||||
state.isAddAnimates = false;
|
||||
console.log(currentBlock.value.animations, '当前组件的动画');
|
||||
};
|
||||
|
||||
// 已添加的动画列表组件
|
||||
const AddedAnimateList = () => (
|
||||
|
@ -104,11 +106,12 @@ export const Animate = defineComponent({
|
|||
<span onClick={() => clickAnimateName(index)} class={'label'}>
|
||||
{item.label}
|
||||
</span>
|
||||
<span
|
||||
onClick={() => runAnimation(item)}
|
||||
class={'el-icon-caret-right play'}
|
||||
title={'播放'}
|
||||
></span>
|
||||
|
||||
<span onClick={() => runAnimation(item)} class={'play'} title={'播放'}>
|
||||
<ElIcon size={20}>
|
||||
<CaretRight></CaretRight>
|
||||
</ElIcon>
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
default: () => (
|
||||
|
@ -129,12 +132,12 @@ export const Animate = defineComponent({
|
|||
</ElRow>
|
||||
<ElSwitch v-model={item.infinite}></ElSwitch> 循环播放
|
||||
</>
|
||||
)
|
||||
),
|
||||
}}
|
||||
</ElAlert>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
);
|
||||
|
||||
// 可添加的动画列表组件
|
||||
const AnimateList = () => (
|
||||
|
@ -157,7 +160,7 @@ export const Animate = defineComponent({
|
|||
</ElTabPane>
|
||||
))}
|
||||
</ElTabs>
|
||||
)
|
||||
);
|
||||
|
||||
return () => (
|
||||
<div ref={target} class={styles.animate}>
|
||||
|
@ -166,7 +169,7 @@ export const Animate = defineComponent({
|
|||
type={'primary'}
|
||||
disabled={!currentBlock.value.animations}
|
||||
plain
|
||||
icon={'el-icon-plus'}
|
||||
icon={Plus}
|
||||
onClick={() => (state.isAddAnimates = true)}
|
||||
>
|
||||
添加动画
|
||||
|
@ -175,7 +178,7 @@ export const Animate = defineComponent({
|
|||
type={'primary'}
|
||||
disabled={!currentBlock.value.animations?.length}
|
||||
plain
|
||||
icon={'el-icon-caret-right'}
|
||||
icon={CaretRight}
|
||||
onClick={() => runAnimation(currentBlock.value.animations)}
|
||||
>
|
||||
播放动画
|
||||
|
@ -184,6 +187,6 @@ export const Animate = defineComponent({
|
|||
</div>
|
||||
<AnimateList v-show={state.isAddAnimates} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
.play {
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
span {
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\attr-editor\components\cross-sortable-options-editor\cross-sortable-options-editor.tsx
|
||||
*/
|
||||
|
||||
import { defineComponent, reactive, computed, PropType } from 'vue'
|
||||
import Draggable from 'vuedraggable'
|
||||
import { defineComponent, reactive, computed, PropType } from 'vue';
|
||||
import Draggable from 'vuedraggable';
|
||||
import {
|
||||
ElInput,
|
||||
ElCheckboxGroup,
|
||||
|
@ -17,80 +17,82 @@ import {
|
|||
ElCollapseItem,
|
||||
ElTabs,
|
||||
ElTabPane,
|
||||
ElForm
|
||||
} from 'element-plus'
|
||||
import { useVModel } from '@vueuse/core'
|
||||
import { isObject } from '@/visual-editor/utils/is'
|
||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData'
|
||||
import { PropConfig } from '../prop-config'
|
||||
import { VisualEditorBlockData, VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
|
||||
import { cloneDeep } from 'lodash'
|
||||
ElForm,
|
||||
ElIcon,
|
||||
} from 'element-plus';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { isObject } from '@/visual-editor/utils/is';
|
||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData';
|
||||
import { PropConfig } from '../prop-config';
|
||||
import { VisualEditorBlockData, VisualEditorComponent } from '@/visual-editor/visual-editor.utils';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { Rank, CirclePlus, Remove } from '@element-plus/icons-vue';
|
||||
|
||||
interface OptionItem extends LabelValue {
|
||||
component?: VisualEditorComponent
|
||||
block?: VisualEditorBlockData
|
||||
component?: VisualEditorComponent;
|
||||
block?: VisualEditorBlockData;
|
||||
}
|
||||
|
||||
export const CrossSortableOptionsEditor = defineComponent({
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Array as PropType<(string | OptionItem)[]>,
|
||||
default: () => []
|
||||
default: () => [],
|
||||
},
|
||||
multiple: Boolean, // 是否多选
|
||||
showItemPropsConfig: Boolean // 是否多选
|
||||
showItemPropsConfig: Boolean, // 是否多选
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const { currentBlock } = useVisualData()
|
||||
const { currentBlock } = useVisualData();
|
||||
|
||||
const state = reactive({
|
||||
list: useVModel(props, 'modelValue', emit),
|
||||
drag: false
|
||||
})
|
||||
drag: false,
|
||||
});
|
||||
|
||||
const checkList = computed({
|
||||
get: () => {
|
||||
const value = currentBlock.value.props.modelValue
|
||||
return Array.isArray(value) ? value : [...new Set(value?.split(','))]
|
||||
const value = currentBlock.value.props.modelValue;
|
||||
return Array.isArray(value) ? value : [...new Set(value?.split(','))];
|
||||
},
|
||||
set(value) {
|
||||
currentBlock.value.props.modelValue = value
|
||||
}
|
||||
})
|
||||
currentBlock.value.props.modelValue = value;
|
||||
},
|
||||
});
|
||||
|
||||
const dragOptions = computed(() => {
|
||||
return {
|
||||
animation: 200,
|
||||
group: 'description',
|
||||
disabled: false,
|
||||
ghostClass: 'ghost'
|
||||
}
|
||||
})
|
||||
ghostClass: 'ghost',
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 复选框值改变时触发
|
||||
*/
|
||||
const onChange = (val: any[]) => {
|
||||
val = val.filter((item) => item !== '')
|
||||
val = val.filter((item) => item !== '');
|
||||
val = props.multiple
|
||||
? val
|
||||
: val.filter((n) => !currentBlock.value.props.modelValue?.includes(n))
|
||||
currentBlock.value.props.modelValue = val.join(',')
|
||||
}
|
||||
: val.filter((n) => !currentBlock.value.props.modelValue?.includes(n));
|
||||
currentBlock.value.props.modelValue = val.join(',');
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number} index - 在某项之前新增一项
|
||||
*/
|
||||
const incrementOption = (index: number) => {
|
||||
const length = state.list.length + 1
|
||||
const length = state.list.length + 1;
|
||||
const newItem = state.list.some((item) => isObject(item))
|
||||
? Object.assign(cloneDeep(state.list[0]), {
|
||||
label: `选项${length}`,
|
||||
value: `选项${length}`
|
||||
value: `选项${length}`,
|
||||
})
|
||||
: ''
|
||||
state.list.splice(index + 1, 0, newItem)
|
||||
}
|
||||
: '';
|
||||
state.list.splice(index + 1, 0, newItem);
|
||||
};
|
||||
|
||||
return () => (
|
||||
<div>
|
||||
|
@ -106,7 +108,7 @@ export const CrossSortableOptionsEditor = defineComponent({
|
|||
component-data={{
|
||||
tag: 'ul',
|
||||
type: 'transition-group',
|
||||
name: !state.drag ? 'flip-list' : null
|
||||
name: !state.drag ? 'flip-list' : null,
|
||||
}}
|
||||
handle=".handle"
|
||||
{...dragOptions.value}
|
||||
|
@ -117,7 +119,9 @@ export const CrossSortableOptionsEditor = defineComponent({
|
|||
{{
|
||||
item: ({ element, index }) => (
|
||||
<div class={'flex items-center justify-between'}>
|
||||
<i class={'el-icon-rank handle cursor-move'}></i>
|
||||
<ElIcon class="handle cursor-move">
|
||||
<Rank></Rank>
|
||||
</ElIcon>
|
||||
{isObject(element) ? (
|
||||
<>
|
||||
<ElCheckbox label={element.value} class={'ml-5px'}>
|
||||
|
@ -128,12 +132,14 @@ export const CrossSortableOptionsEditor = defineComponent({
|
|||
v-model={element.label}
|
||||
class={'my-12px mx-3px'}
|
||||
style={{ width: '108px' }}
|
||||
size="small"
|
||||
></ElInput>
|
||||
value:
|
||||
<ElInput
|
||||
v-model={element.value}
|
||||
class={'my-12px mx-3px'}
|
||||
style={{ width: '106px' }}
|
||||
size="small"
|
||||
></ElInput>
|
||||
</>
|
||||
) : (
|
||||
|
@ -141,20 +147,25 @@ export const CrossSortableOptionsEditor = defineComponent({
|
|||
v-model={state.list[index]}
|
||||
class={'m-12px'}
|
||||
style={{ width: '270px' }}
|
||||
size="small"
|
||||
></ElInput>
|
||||
)}
|
||||
<div class={'flex flex-col'}>
|
||||
<i
|
||||
class={'el-icon-circle-plus-outline hover:text-blue-400 cursor-pointer'}
|
||||
<ElIcon
|
||||
class="hover:text-blue-400 cursor-pointer"
|
||||
onClick={() => incrementOption(index)}
|
||||
></i>
|
||||
<i
|
||||
class={'el-icon-remove-outline hover:text-red-500 cursor-pointer'}
|
||||
>
|
||||
<CirclePlus></CirclePlus>
|
||||
</ElIcon>
|
||||
<ElIcon
|
||||
class="hover:text-red-500 cursor-pointer"
|
||||
onClick={() => state.list.splice(index, 1)}
|
||||
></i>
|
||||
>
|
||||
<Remove></Remove>
|
||||
</ElIcon>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
),
|
||||
}}
|
||||
</Draggable>
|
||||
</ElCheckboxGroup>
|
||||
|
@ -164,7 +175,7 @@ export const CrossSortableOptionsEditor = defineComponent({
|
|||
<ElTabs type={'border-card'}>
|
||||
{state.list.map((item: OptionItem) => (
|
||||
<ElTabPane label={item.label} key={item.label}>
|
||||
<ElForm size="mini" labelPosition={'left'}>
|
||||
<ElForm labelPosition={'left'}>
|
||||
<PropConfig component={item.component} block={item.block} />
|
||||
</ElForm>
|
||||
</ElTabPane>
|
||||
|
@ -174,6 +185,6 @@ export const CrossSortableOptionsEditor = defineComponent({
|
|||
</ElCollapse>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\attr-editor\components\prop-config\index.tsx
|
||||
*/
|
||||
|
||||
import { computed, defineComponent, PropType } from 'vue'
|
||||
import { computed, defineComponent, PropType } from 'vue';
|
||||
import {
|
||||
ElColorPicker,
|
||||
ElInput,
|
||||
|
@ -17,51 +17,53 @@ import {
|
|||
ElCascader,
|
||||
ElInputNumber,
|
||||
ElFormItem,
|
||||
ElPopover
|
||||
} from 'element-plus'
|
||||
import { useDotProp } from '@/visual-editor/hooks/useDotProp'
|
||||
import { VisualEditorProps, VisualEditorPropsType } from '@/visual-editor/visual-editor.props'
|
||||
import { TablePropEditor, CrossSortableOptionsEditor } from '../../components'
|
||||
import { cloneDeep } from 'lodash'
|
||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData'
|
||||
import { VisualEditorBlockData, VisualEditorComponent } from '@/visual-editor/visual-editor.utils'
|
||||
ElTooltip,
|
||||
ElIcon,
|
||||
} from 'element-plus';
|
||||
import { useDotProp } from '@/visual-editor/hooks/useDotProp';
|
||||
import { VisualEditorProps, VisualEditorPropsType } from '@/visual-editor/visual-editor.props';
|
||||
import { TablePropEditor, CrossSortableOptionsEditor } from '../../components';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData';
|
||||
import { VisualEditorBlockData, VisualEditorComponent } from '@/visual-editor/visual-editor.utils';
|
||||
import { Warning } from '@element-plus/icons-vue';
|
||||
|
||||
export const PropConfig = defineComponent({
|
||||
props: {
|
||||
component: {
|
||||
type: Object as PropType<VisualEditorComponent>,
|
||||
default: () => ({})
|
||||
default: () => ({}),
|
||||
},
|
||||
block: {
|
||||
type: Object as PropType<VisualEditorBlockData>,
|
||||
default: () => ({})
|
||||
}
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const { jsonData } = useVisualData()
|
||||
const { jsonData } = useVisualData();
|
||||
/**
|
||||
* @description 模型集合
|
||||
*/
|
||||
const models = computed(() => cloneDeep(jsonData.models))
|
||||
const models = computed(() => cloneDeep(jsonData.models));
|
||||
|
||||
const renderPropItem = (propName: string, propConfig: VisualEditorProps) => {
|
||||
const { propObj, prop } = useDotProp(props.block.props, propName)
|
||||
const { propObj, prop } = useDotProp(props.block.props, propName);
|
||||
|
||||
propObj[prop] ??= propConfig.defaultValue
|
||||
propObj[prop] ??= propConfig.defaultValue;
|
||||
|
||||
return {
|
||||
[VisualEditorPropsType.input]: () => {
|
||||
if (!Object.is(propObj[prop], undefined) && !Object.is(propObj[prop], null)) {
|
||||
propObj[prop] = `${propObj[prop]}`
|
||||
propObj[prop] = `${propObj[prop]}`;
|
||||
}
|
||||
return (
|
||||
<ElInput v-model={propObj[prop]} placeholder={propConfig.tips || propConfig.label} />
|
||||
)
|
||||
);
|
||||
},
|
||||
[VisualEditorPropsType.inputNumber]: () => {
|
||||
const parseRes = parseFloat(propObj[prop])
|
||||
propObj[prop] = Number.isNaN(parseRes) ? 0 : parseRes
|
||||
return <ElInputNumber v-model={propObj[prop]} />
|
||||
const parseRes = parseFloat(propObj[prop]);
|
||||
propObj[prop] = Number.isNaN(parseRes) ? 0 : parseRes;
|
||||
return <ElInputNumber v-model={propObj[prop]} />;
|
||||
},
|
||||
[VisualEditorPropsType.switch]: () => <ElSwitch v-model={propObj[prop]} />,
|
||||
[VisualEditorPropsType.color]: () => <ElColorPicker v-model={propObj[prop]} />,
|
||||
|
@ -90,15 +92,15 @@ export const PropConfig = defineComponent({
|
|||
children: 'entitys',
|
||||
label: 'name',
|
||||
value: 'key',
|
||||
expandTrigger: 'hover'
|
||||
expandTrigger: 'hover',
|
||||
}}
|
||||
placeholder="请选择绑定的请求数据"
|
||||
v-model={propObj[prop]}
|
||||
options={models.value}
|
||||
></ElCascader>
|
||||
)
|
||||
}[propConfig.type]()
|
||||
}
|
||||
),
|
||||
}[propConfig.type]();
|
||||
};
|
||||
|
||||
return () => {
|
||||
return Object.entries(props.component.props ?? {}).map(([propName, propConfig]) => (
|
||||
|
@ -110,7 +112,7 @@ export const PropConfig = defineComponent({
|
|||
? {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start'
|
||||
alignItems: 'flex-start',
|
||||
}
|
||||
: {}
|
||||
}
|
||||
|
@ -119,20 +121,24 @@ export const PropConfig = defineComponent({
|
|||
label: () => (
|
||||
<>
|
||||
{propConfig.tips && (
|
||||
<ElPopover width={200} trigger={'hover'} content={propConfig.tips}>
|
||||
{{
|
||||
reference: () => <i class={'el-icon-warning-outline'}></i>
|
||||
}}
|
||||
</ElPopover>
|
||||
<ElTooltip
|
||||
placement="left-start"
|
||||
popper-class="max-w-200px"
|
||||
content={propConfig.tips}
|
||||
>
|
||||
<ElIcon>
|
||||
<Warning />
|
||||
</ElIcon>
|
||||
</ElTooltip>
|
||||
)}
|
||||
{propConfig.label}
|
||||
</>
|
||||
),
|
||||
default: () => renderPropItem(propName, propConfig)
|
||||
default: () => renderPropItem(propName, propConfig),
|
||||
}}
|
||||
</ElFormItem>
|
||||
</>
|
||||
))
|
||||
}
|
||||
}
|
||||
})
|
||||
));
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -6,17 +6,18 @@
|
|||
* @Description: 组件属性编辑器
|
||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\attr-editor\index.tsx
|
||||
*/
|
||||
import { defineComponent, computed, watch } from 'vue'
|
||||
import { ElForm, ElFormItem, ElPopover, ElRadioGroup, ElRadioButton } from 'element-plus'
|
||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData'
|
||||
import { FormatInputNumber } from '@/visual-editor/components/common/format-input-number'
|
||||
import { PropConfig } from './components/prop-config'
|
||||
import { defineComponent, computed, watch } from 'vue';
|
||||
import { ElForm, ElFormItem, ElPopover, ElRadioGroup, ElRadioButton, ElIcon } from 'element-plus';
|
||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData';
|
||||
import { FormatInputNumber } from '@/visual-editor/components/common/format-input-number';
|
||||
import { PropConfig } from './components/prop-config';
|
||||
import { Warning } from '@element-plus/icons-vue';
|
||||
|
||||
export const AttrEditor = defineComponent({
|
||||
setup() {
|
||||
const { visualConfig, currentBlock } = useVisualData()
|
||||
const { visualConfig, currentBlock } = useVisualData();
|
||||
|
||||
const compPaddingAttrs = ['paddingTop', 'paddingLeft', 'paddingRight', 'paddingBottom']
|
||||
const compPaddingAttrs = ['paddingTop', 'paddingLeft', 'paddingRight', 'paddingBottom'];
|
||||
|
||||
/**
|
||||
* @description 监听组件padding值的变化
|
||||
|
@ -24,16 +25,16 @@ export const AttrEditor = defineComponent({
|
|||
watch(
|
||||
compPaddingAttrs.map((item) => () => currentBlock.value.styles?.[item]),
|
||||
(val: string[]) => {
|
||||
const isSame = val.every((item) => currentBlock.value.styles?.tempPadding == item)
|
||||
const isSame = val.every((item) => currentBlock.value.styles?.tempPadding == item);
|
||||
if (isSame || new Set(val).size === 1) {
|
||||
if (Reflect.has(currentBlock.value, 'styles')) {
|
||||
currentBlock.value.styles.tempPadding = val[0]
|
||||
currentBlock.value.styles.tempPadding = val[0];
|
||||
}
|
||||
} else {
|
||||
currentBlock.value.styles.tempPadding = ''
|
||||
currentBlock.value.styles.tempPadding = '';
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @description 总的组件padding变化时进行的操作
|
||||
|
@ -41,18 +42,18 @@ export const AttrEditor = defineComponent({
|
|||
const compPadding = computed({
|
||||
get: () => currentBlock.value.styles?.tempPadding,
|
||||
set(val) {
|
||||
compPaddingAttrs.forEach((item) => (currentBlock.value.styles[item] = val))
|
||||
currentBlock.value.styles.tempPadding = val
|
||||
}
|
||||
})
|
||||
compPaddingAttrs.forEach((item) => (currentBlock.value.styles[item] = val));
|
||||
currentBlock.value.styles.tempPadding = val;
|
||||
},
|
||||
});
|
||||
|
||||
// 表单项
|
||||
const FormEditor = () => {
|
||||
const content: JSX.Element[] = []
|
||||
const content: JSX.Element[] = [];
|
||||
if (currentBlock.value) {
|
||||
const { componentKey } = currentBlock.value
|
||||
const component = visualConfig.componentMap[componentKey]
|
||||
console.log('props.block:', currentBlock.value)
|
||||
const { componentKey } = currentBlock.value;
|
||||
const component = visualConfig.componentMap[componentKey];
|
||||
console.log('props.block:', currentBlock.value);
|
||||
content.push(
|
||||
<>
|
||||
<ElFormItem label="组件ID" labelWidth={'76px'}>
|
||||
|
@ -60,23 +61,28 @@ export const AttrEditor = defineComponent({
|
|||
<ElPopover
|
||||
width={200}
|
||||
trigger="hover"
|
||||
effect="dark"
|
||||
content={`你可以利用该组件ID。对该组件进行获取和设置其属性,组件可用属性可在控制台输入:$$refs.${currentBlock.value._vid} 进行查看`}
|
||||
>
|
||||
{{
|
||||
reference: () => <i class={'el-icon-warning-outline ml-6px'}></i>
|
||||
reference: () => (
|
||||
<ElIcon class={'ml-6px'}>
|
||||
<Warning />
|
||||
</ElIcon>
|
||||
),
|
||||
}}
|
||||
</ElPopover>
|
||||
</ElFormItem>
|
||||
</>
|
||||
)
|
||||
</>,
|
||||
);
|
||||
if (!!component) {
|
||||
if (!!component.props) {
|
||||
content.push(<PropConfig component={component} block={currentBlock.value} />)
|
||||
content.push(<PropConfig component={component} block={currentBlock.value} />);
|
||||
{
|
||||
currentBlock.value.showStyleConfig &&
|
||||
content.push(
|
||||
<ElFormItem label={'组件对齐方式'} labelWidth={'90px'}>
|
||||
<ElRadioGroup v-model={currentBlock.value.styles.justifyContent} size="mini">
|
||||
<ElRadioGroup v-model={currentBlock.value.styles.justifyContent}>
|
||||
<ElRadioButton label="flex-start">{'左对齐'}</ElRadioButton>
|
||||
<ElRadioButton label="center">{'居中'}</ElRadioButton>
|
||||
<ElRadioButton label="flex-end">{'右对齐'}</ElRadioButton>
|
||||
|
@ -86,7 +92,7 @@ export const AttrEditor = defineComponent({
|
|||
{{
|
||||
label: () => (
|
||||
<div class={'flex justify-between mb-2'}>
|
||||
<div>组件内边距</div>
|
||||
<div class="mr-3">组件内边距</div>
|
||||
<FormatInputNumber v-model={compPadding.value} class={'!w-100px'} />
|
||||
</div>
|
||||
),
|
||||
|
@ -112,27 +118,25 @@ export const AttrEditor = defineComponent({
|
|||
class={'!w-100px col-span-full col-start-2'}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
),
|
||||
}}
|
||||
</ElFormItem>
|
||||
)
|
||||
</ElFormItem>,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<ElForm size="mini" labelPosition={'left'}>
|
||||
{content}
|
||||
</ElForm>
|
||||
<ElForm labelPosition={'left'}>{content}</ElForm>
|
||||
</>
|
||||
)
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return () => (
|
||||
<>
|
||||
<FormEditor />
|
||||
</>
|
||||
)
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
* @Description: 事件-动作
|
||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\event-action\index.tsx
|
||||
*/
|
||||
import { computed, ref, defineComponent, reactive } from 'vue'
|
||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData'
|
||||
import { computed, ref, defineComponent, reactive } from 'vue';
|
||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData';
|
||||
import {
|
||||
ElForm,
|
||||
ElFormItem,
|
||||
|
@ -19,16 +19,16 @@ import {
|
|||
ElCascader,
|
||||
ElCollapse,
|
||||
ElCollapseItem,
|
||||
ElPopconfirm
|
||||
} from 'element-plus'
|
||||
import type { Action } from '@/visual-editor/visual-editor.utils'
|
||||
import { generateNanoid } from '@/visual-editor/utils/'
|
||||
import { useModal } from '@/visual-editor/hooks/useModal'
|
||||
import { cloneDeep } from 'lodash'
|
||||
ElPopconfirm,
|
||||
} from 'element-plus';
|
||||
import type { Action } from '@/visual-editor/visual-editor.utils';
|
||||
import { generateNanoid } from '@/visual-editor/utils/';
|
||||
import { useModal } from '@/visual-editor/hooks/useModal';
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
interface IState {
|
||||
activeNames: string[]
|
||||
ruleForm: Action
|
||||
activeNames: string[];
|
||||
ruleForm: Action;
|
||||
}
|
||||
/**
|
||||
* @description 创建一个空的动作处理对象
|
||||
|
@ -36,8 +36,8 @@ interface IState {
|
|||
const createEmptyActionHandle = () => ({
|
||||
key: generateNanoid(),
|
||||
name: '',
|
||||
link: []
|
||||
})
|
||||
link: [],
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 新增一个空的事件
|
||||
|
@ -46,24 +46,24 @@ const createEmptyAction = (): Action => ({
|
|||
key: generateNanoid(),
|
||||
name: '',
|
||||
event: '',
|
||||
handle: [createEmptyActionHandle()]
|
||||
})
|
||||
handle: [createEmptyActionHandle()],
|
||||
});
|
||||
|
||||
export const EventAction = defineComponent({
|
||||
setup() {
|
||||
const { currentBlock, currentPage, jsonData } = useVisualData()
|
||||
const { currentBlock, currentPage, jsonData } = useVisualData();
|
||||
/**
|
||||
* @description 是否处于编辑状态
|
||||
*/
|
||||
const isEdit = computed(() =>
|
||||
currentBlock.value.actions?.some((item) => item.key === state.ruleForm.key)
|
||||
)
|
||||
const ruleFormRef = ref<InstanceType<typeof ElForm>>()
|
||||
currentBlock.value.actions?.some((item) => item.key === state.ruleForm.key),
|
||||
);
|
||||
const ruleFormRef = ref<InstanceType<typeof ElForm>>();
|
||||
|
||||
const state = reactive<IState>({
|
||||
activeNames: [],
|
||||
ruleForm: createEmptyAction()
|
||||
})
|
||||
ruleForm: createEmptyAction(),
|
||||
});
|
||||
|
||||
/**
|
||||
* @description 可绑定的动作
|
||||
|
@ -73,17 +73,17 @@ export const EventAction = defineComponent({
|
|||
label: '全局',
|
||||
value: 'global',
|
||||
children: Object.keys(jsonData.actions).map((actionKey) => {
|
||||
const item = cloneDeep(jsonData.actions[actionKey])
|
||||
item.value = actionKey
|
||||
item.label = item.name
|
||||
const arrKey = Object.keys(item).find((key) => Array.isArray(item[key]))
|
||||
const item = cloneDeep(jsonData.actions[actionKey]);
|
||||
item.value = actionKey;
|
||||
item.label = item.name;
|
||||
const arrKey = Object.keys(item).find((key) => Array.isArray(item[key]));
|
||||
item.children = (item[arrKey] || []).map((item: any) => {
|
||||
item.label = item.name
|
||||
item.value = item.key
|
||||
return item
|
||||
})
|
||||
return item
|
||||
})
|
||||
item.label = item.name;
|
||||
item.value = item.key;
|
||||
return item;
|
||||
});
|
||||
return item;
|
||||
}),
|
||||
},
|
||||
{
|
||||
label: '组件',
|
||||
|
@ -91,77 +91,77 @@ export const EventAction = defineComponent({
|
|||
children: cloneDeep(currentPage.value.blocks)
|
||||
.filter((item) => item.actions?.length)
|
||||
.map((item) => {
|
||||
item.value = item._vid
|
||||
item.label = item.label
|
||||
item.value = item._vid;
|
||||
item.label = item.label;
|
||||
item.children = (item.actions || []).map((item: any) => {
|
||||
item.label = item.name
|
||||
item.value = item.key
|
||||
return item
|
||||
})
|
||||
return item
|
||||
})
|
||||
}
|
||||
])
|
||||
item.label = item.name;
|
||||
item.value = item.key;
|
||||
return item;
|
||||
});
|
||||
return item;
|
||||
}),
|
||||
},
|
||||
]);
|
||||
|
||||
/**
|
||||
* @description 获取动作路径
|
||||
*/
|
||||
const getActionPath = (link: string[]) => {
|
||||
const result: string[] = []
|
||||
const result: string[] = [];
|
||||
link.reduce((prev, curr) => {
|
||||
const target = prev?.find((item) => item.value == curr)
|
||||
result.push(`${target?.label}`)
|
||||
return target?.children
|
||||
}, actionOptions.value)
|
||||
return result.join(' => ')
|
||||
}
|
||||
const target = prev?.find((item) => item.value == curr);
|
||||
result.push(`${target?.label}`);
|
||||
return target?.children;
|
||||
}, actionOptions.value);
|
||||
return result.join(' => ');
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 删除某个动作
|
||||
*/
|
||||
const deleteActionItem = (index: number) => {
|
||||
currentBlock.value.actions.splice(index, 1)
|
||||
}
|
||||
currentBlock.value.actions.splice(index, 1);
|
||||
};
|
||||
/**
|
||||
* @description 删除事件的某个动作
|
||||
*/
|
||||
const deleteActionHandleItem = (index: number) => {
|
||||
state.ruleForm.handle.splice(index, 1)
|
||||
}
|
||||
state.ruleForm.handle.splice(index, 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 给组件新增一个事件模型
|
||||
*/
|
||||
const addActionItem = () => {
|
||||
state.ruleForm = createEmptyAction()
|
||||
showOperateModal()
|
||||
}
|
||||
state.ruleForm = createEmptyAction();
|
||||
showOperateModal();
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 给事件新增一个空的动作
|
||||
*/
|
||||
const addActionHanleItem = () => {
|
||||
state.ruleForm.handle.push(createEmptyActionHandle())
|
||||
}
|
||||
state.ruleForm.handle.push(createEmptyActionHandle());
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 编辑事件
|
||||
*/
|
||||
const showEditActionModal = (action: Action) => {
|
||||
state.ruleForm = cloneDeep(action)
|
||||
showOperateModal()
|
||||
}
|
||||
state.ruleForm = cloneDeep(action);
|
||||
showOperateModal();
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 显示操作动作的模态框
|
||||
*/
|
||||
const showOperateModal = () => {
|
||||
const operateType = isEdit.value ? '编辑' : '新增'
|
||||
const operateType = isEdit.value ? '编辑' : '新增';
|
||||
useModal({
|
||||
title: `${operateType}动作`,
|
||||
props: { width: 600 },
|
||||
content: () => (
|
||||
<ElForm model={state.ruleForm} ref={ruleFormRef} label-width="100px" size={'mini'}>
|
||||
<ElForm model={state.ruleForm} ref={ruleFormRef} label-width="100px">
|
||||
<ElFormItem
|
||||
label="事件"
|
||||
prop={'event'}
|
||||
|
@ -186,7 +186,7 @@ export const EventAction = defineComponent({
|
|||
</ElFormItem>
|
||||
{!state.ruleForm.handle?.length && (
|
||||
<ElFormItem>
|
||||
<ElButton onClick={addActionHanleItem} type={'primary'} size={'mini'}>
|
||||
<ElButton onClick={addActionHanleItem} type={'primary'}>
|
||||
添加动作
|
||||
</ElButton>
|
||||
</ElFormItem>
|
||||
|
@ -207,19 +207,15 @@ export const EventAction = defineComponent({
|
|||
<ElInput v-model={handleItem.name} placeholder={'请输入动作名称'}></ElInput>
|
||||
</ElFormItem>
|
||||
<div>
|
||||
<ElButton
|
||||
onClick={() => deleteActionHandleItem(index)}
|
||||
type={'danger'}
|
||||
size={'mini'}
|
||||
>
|
||||
<ElButton onClick={() => deleteActionHandleItem(index)} type={'danger'}>
|
||||
删除
|
||||
</ElButton>
|
||||
<ElButton onClick={addActionHanleItem} type={'primary'} size={'mini'}>
|
||||
<ElButton onClick={addActionHanleItem} type={'primary'}>
|
||||
添加
|
||||
</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
),
|
||||
}}
|
||||
>
|
||||
<ElFormItem
|
||||
|
@ -244,35 +240,30 @@ export const EventAction = defineComponent({
|
|||
ruleFormRef.value?.validate((valid) => {
|
||||
if (valid) {
|
||||
const index = currentBlock.value.actions.findIndex(
|
||||
(item) => item.key == state.ruleForm.key
|
||||
)
|
||||
(item) => item.key == state.ruleForm.key,
|
||||
);
|
||||
if (index === -1) {
|
||||
currentBlock.value.actions.push(state.ruleForm)
|
||||
currentBlock.value.actions.push(state.ruleForm);
|
||||
} else {
|
||||
currentBlock.value.actions.splice(index, 1, state.ruleForm)
|
||||
currentBlock.value.actions.splice(index, 1, state.ruleForm);
|
||||
}
|
||||
state.ruleForm = createEmptyAction()
|
||||
resolve('submit!')
|
||||
state.ruleForm = createEmptyAction();
|
||||
resolve('submit!');
|
||||
} else {
|
||||
reject()
|
||||
console.log('error submit!!')
|
||||
return false
|
||||
reject();
|
||||
console.log('error submit!!');
|
||||
return false;
|
||||
}
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
},
|
||||
onCancel: () => (state.ruleForm = createEmptyAction())
|
||||
})
|
||||
}
|
||||
onCancel: () => (state.ruleForm = createEmptyAction()),
|
||||
});
|
||||
};
|
||||
|
||||
return () => (
|
||||
<>
|
||||
<ElButton
|
||||
onClick={addActionItem}
|
||||
disabled={!currentBlock.value.actions}
|
||||
type={'primary'}
|
||||
size={'mini'}
|
||||
>
|
||||
<ElButton onClick={addActionItem} disabled={!currentBlock.value.actions} type="primary">
|
||||
添加事件
|
||||
</ElButton>
|
||||
|
||||
|
@ -290,30 +281,22 @@ export const EventAction = defineComponent({
|
|||
onConfirm={() => deleteActionItem(index)}
|
||||
>
|
||||
{{
|
||||
reference: () => (
|
||||
<ElButton type={'danger'} size={'mini'}>
|
||||
删除
|
||||
</ElButton>
|
||||
)
|
||||
reference: () => <ElButton type={'danger'}>删除</ElButton>,
|
||||
}}
|
||||
</ElPopconfirm>
|
||||
<ElButton
|
||||
onClick={() => showEditActionModal(actionItem)}
|
||||
type={'primary'}
|
||||
size={'mini'}
|
||||
>
|
||||
<ElButton onClick={() => showEditActionModal(actionItem)} type="primary">
|
||||
编辑
|
||||
</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
),
|
||||
}}
|
||||
>
|
||||
<ElCollapse v-model={state.activeNames}>
|
||||
{actionItem.handle.map((item, index) => (
|
||||
<ElCollapseItem title={`${index + 1}. ${item.name}`} key={item.key} name={item.key}>
|
||||
{{
|
||||
default: () => <div>动作路径:{getActionPath(item.link)}</div>
|
||||
default: () => <div>动作路径:{getActionPath(item.link)}</div>,
|
||||
}}
|
||||
</ElCollapseItem>
|
||||
))}
|
||||
|
@ -321,6 +304,6 @@ export const EventAction = defineComponent({
|
|||
</ElCard>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -6,8 +6,9 @@
|
|||
* @Description: 表单规则
|
||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\form-rule\index.tsx
|
||||
*/
|
||||
import { defineComponent } from 'vue'
|
||||
import { ElCard, ElTooltip } from 'element-plus'
|
||||
import { defineComponent } from 'vue';
|
||||
import { ElCard, ElTooltip } from 'element-plus';
|
||||
import { QuestionFilled } from '@element-plus/icons-vue';
|
||||
|
||||
export const FormRule = defineComponent({
|
||||
setup() {
|
||||
|
@ -19,11 +20,13 @@ export const FormRule = defineComponent({
|
|||
<div class="flex justify-between">
|
||||
<span>设置关联规则</span>
|
||||
<ElTooltip content="当前面题目选中某些选项时才出现此题" placement="bottom-end">
|
||||
<i class={'el-icon-question'}></i>
|
||||
<el-icon>
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</ElTooltip>
|
||||
</div>
|
||||
),
|
||||
default: () => <div>暂无规则</div>
|
||||
default: () => <div>暂无规则</div>,
|
||||
}}
|
||||
</ElCard>
|
||||
<ElCard shadow={'always'} bodyStyle={{ padding: 1 ? '0' : '20px' }} class={'mb-20px'}>
|
||||
|
@ -35,14 +38,16 @@ export const FormRule = defineComponent({
|
|||
content="当前面题目选择某些选项时才出现此题的某些选项 "
|
||||
placement="bottom-end"
|
||||
>
|
||||
<i class={'el-icon-question'}></i>
|
||||
<el-icon>
|
||||
<QuestionFilled />
|
||||
</el-icon>
|
||||
</ElTooltip>
|
||||
</div>
|
||||
),
|
||||
default: () => null
|
||||
default: () => null,
|
||||
}}
|
||||
</ElCard>
|
||||
</>
|
||||
)
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -6,25 +6,26 @@
|
|||
* @Description: 当前页面配置
|
||||
* @FilePath: \vite-vue3-lowcode\src\visual-editor\components\right-attribute-panel\components\page-setting\pageSetting.tsx
|
||||
*/
|
||||
import { defineComponent } from 'vue'
|
||||
import { ElForm, ElFormItem, ElInput, ElUpload, ElColorPicker, ElSwitch } from 'element-plus'
|
||||
import styles from './styles.module.scss'
|
||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData'
|
||||
import { defineComponent } from 'vue';
|
||||
import { ElForm, ElFormItem, ElInput, ElUpload, ElColorPicker, ElSwitch } from 'element-plus';
|
||||
import styles from './styles.module.scss';
|
||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData';
|
||||
import { Plus } from '@element-plus/icons-vue';
|
||||
|
||||
export const PageSetting = defineComponent({
|
||||
setup() {
|
||||
const { currentPage } = useVisualData()
|
||||
const { currentPage } = useVisualData();
|
||||
|
||||
const pageConfig = currentPage.value.config
|
||||
const pageConfig = currentPage.value.config;
|
||||
|
||||
const beforeUpload = (file: File) => {
|
||||
console.log(file, '要上传的文件')
|
||||
const fileReader = new FileReader()
|
||||
console.log(file, '要上传的文件');
|
||||
const fileReader = new FileReader();
|
||||
fileReader.onload = (event) => {
|
||||
pageConfig.bgImage = event.target?.result as string
|
||||
}
|
||||
fileReader.readAsDataURL(file)
|
||||
}
|
||||
pageConfig.bgImage = event.target?.result as string;
|
||||
};
|
||||
fileReader.readAsDataURL(file);
|
||||
};
|
||||
|
||||
return () => (
|
||||
<>
|
||||
|
@ -42,11 +43,13 @@ export const PageSetting = defineComponent({
|
|||
{pageConfig.bgImage ? (
|
||||
<img src={pageConfig.bgImage} />
|
||||
) : (
|
||||
<i class="el-icon-plus uploader-icon"></i>
|
||||
<el-icon class="uploader-icon">
|
||||
<Plus />
|
||||
</el-icon>
|
||||
)}
|
||||
</ElUpload>
|
||||
</ElForm>
|
||||
</>
|
||||
)
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -72,7 +72,7 @@ $boxShadow: -2px 0 4px 0 rgb(0 0 0 / 10%);
|
|||
font-size: 12px;
|
||||
}
|
||||
|
||||
.el-form-item--mini .el-form-item__content {
|
||||
.el-form-item .el-form-item__content {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
|
|
|
@ -8,36 +8,37 @@
|
|||
* RightAttributePanel
|
||||
*/
|
||||
|
||||
import { defineComponent, reactive, watch } from 'vue'
|
||||
import styles from './index.module.scss'
|
||||
import { ElTabPane, ElTabs } from 'element-plus'
|
||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData'
|
||||
import { AttrEditor, Animate, PageSetting, EventAction, FormRule } from './components'
|
||||
import { defineComponent, reactive, watch } from 'vue';
|
||||
import styles from './index.module.scss';
|
||||
import { ElTabPane, ElTabs } from 'element-plus';
|
||||
import { useVisualData } from '@/visual-editor/hooks/useVisualData';
|
||||
import { AttrEditor, Animate, PageSetting, EventAction, FormRule } from './components';
|
||||
import { DArrowLeft, DArrowRight } from '@element-plus/icons-vue';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RightAttributePanel',
|
||||
setup() {
|
||||
const { currentBlock } = useVisualData()
|
||||
const { currentBlock } = useVisualData();
|
||||
|
||||
const state = reactive({
|
||||
activeName: 'attr',
|
||||
isOpen: true
|
||||
})
|
||||
isOpen: true,
|
||||
});
|
||||
|
||||
watch(
|
||||
() => currentBlock.value.label,
|
||||
(newLabel) => {
|
||||
if (!newLabel?.startsWith('表单') && state.activeName == 'form-rule') {
|
||||
state.activeName = 'attr'
|
||||
state.activeName = 'attr';
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
return () => (
|
||||
<>
|
||||
<div class={[styles.drawer, { [styles.isOpen]: state.isOpen }]}>
|
||||
<div class={styles.floatingActionBtn} onClick={() => (state.isOpen = !state.isOpen)}>
|
||||
<i class={`el-icon-d-arrow-${state.isOpen ? 'right' : 'left'}`}></i>
|
||||
{state.isOpen ? <DArrowRight /> : <DArrowLeft />}
|
||||
</div>
|
||||
<div class={styles.attrs}>
|
||||
<ElTabs
|
||||
|
@ -67,6 +68,6 @@ export default defineComponent({
|
|||
</div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,66 +1,68 @@
|
|||
import type { VisualEditorProps } from './visual-editor.props'
|
||||
import { inject, provide } from 'vue'
|
||||
import type { CSSProperties } from 'vue'
|
||||
import { useDotProp } from '@/visual-editor/hooks/useDotProp'
|
||||
import type { RequestEnum, ContentTypeEnum } from '@/enums/httpEnum'
|
||||
import { generateNanoid } from '@/visual-editor/utils'
|
||||
import type { VisualEditorProps } from './visual-editor.props';
|
||||
import { inject, provide } from 'vue';
|
||||
import type { CSSProperties } from 'vue';
|
||||
import { useDotProp } from '@/visual-editor/hooks/useDotProp';
|
||||
import type { RequestEnum, ContentTypeEnum } from '@/enums/httpEnum';
|
||||
import { generateNanoid } from '@/visual-editor/utils';
|
||||
|
||||
/**
|
||||
* @description 组件属性
|
||||
*/
|
||||
export interface VisualEditorBlockData {
|
||||
/** 组件id 时间戳, 组件唯一标识 */
|
||||
_vid: string
|
||||
_vid: string;
|
||||
/** 组件所属的模块(基础组件、容器组件) */
|
||||
moduleName: keyof ComponentModules
|
||||
moduleName: keyof ComponentModules;
|
||||
/** 映射 VisualEditorConfig 中 componentMap 的 component对象 */
|
||||
componentKey: string
|
||||
componentKey: string;
|
||||
/** 组件标签名称 */
|
||||
label: string
|
||||
label: string;
|
||||
/** 是否需要调整位置 */
|
||||
adjustPosition: boolean
|
||||
adjustPosition: boolean;
|
||||
/** 当前是否为选中状态 */
|
||||
focus: boolean
|
||||
focus: boolean;
|
||||
/** 当前组件的样式 */
|
||||
styles: CSSProperties
|
||||
styles: CSSProperties & {
|
||||
tempPadding?: string;
|
||||
};
|
||||
/** 是否调整过宽度或者高度 */
|
||||
hasResize: boolean
|
||||
hasResize: boolean;
|
||||
/** 组件的设计属性 */
|
||||
props: Record<string, any>
|
||||
props: Record<string, any>;
|
||||
/** 绑定的字段 */
|
||||
model: Record<string, string>
|
||||
model: Record<string, string>;
|
||||
/** 组件是否可以被拖拽 */
|
||||
draggable: boolean
|
||||
draggable: boolean;
|
||||
/** 是否显示组件样式配置项 */
|
||||
showStyleConfig?: boolean
|
||||
showStyleConfig?: boolean;
|
||||
/** 动画集 */
|
||||
animations?: Animation[]
|
||||
animations?: Animation[];
|
||||
/** 组件动作集合 */
|
||||
actions: Action[]
|
||||
actions: Action[];
|
||||
/** 组件事件集合 */
|
||||
events: { label: string; value: string }[]
|
||||
[prop: string]: any
|
||||
events: { label: string; value: string }[];
|
||||
[prop: string]: any;
|
||||
}
|
||||
/**
|
||||
* @description 组件动作事件处理
|
||||
*/
|
||||
export interface ActionHandle {
|
||||
key: string
|
||||
name: string
|
||||
link: string[]
|
||||
key: string;
|
||||
name: string;
|
||||
link: string[];
|
||||
data?: {
|
||||
bind?: string
|
||||
recv?: string
|
||||
}
|
||||
bind?: string;
|
||||
recv?: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @description 组件动作
|
||||
*/
|
||||
export interface Action {
|
||||
key: string
|
||||
name: string
|
||||
event: string
|
||||
handle: ActionHandle[]
|
||||
key: string;
|
||||
name: string;
|
||||
event: string;
|
||||
handle: ActionHandle[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,80 +70,80 @@ export interface Action {
|
|||
*/
|
||||
export interface PageConfig {
|
||||
/** 背景图片 */
|
||||
bgImage: string
|
||||
bgImage: string;
|
||||
/** 背景颜色 */
|
||||
bgColor: string
|
||||
bgColor: string;
|
||||
/** 是否缓存当前页面 */
|
||||
keepAlive: boolean
|
||||
keepAlive: boolean;
|
||||
}
|
||||
/**
|
||||
* @description 页面对象
|
||||
*/
|
||||
export interface VisualEditorPage {
|
||||
/** 页面标题 */
|
||||
title: string
|
||||
title: string;
|
||||
/** 页面路径 */
|
||||
path: string
|
||||
path: string;
|
||||
/** 404是重定向到默认页面 */
|
||||
isDefault?: boolean
|
||||
isDefault?: boolean;
|
||||
/** 页面配置 */
|
||||
config: PageConfig
|
||||
config: PageConfig;
|
||||
/** 当前页面的所有组件 */
|
||||
blocks: VisualEditorBlockData[]
|
||||
blocks: VisualEditorBlockData[];
|
||||
}
|
||||
/**
|
||||
* @description 可以认为是 路由=>页面
|
||||
*/
|
||||
export interface VisualEditorPages {
|
||||
[path: string]: VisualEditorPage
|
||||
[path: string]: VisualEditorPage;
|
||||
}
|
||||
/**
|
||||
* @description 实体类型
|
||||
*/
|
||||
export type EntityType = {
|
||||
/** 绑定的字段 输入 */
|
||||
key: string
|
||||
key: string;
|
||||
/** 实体名称 输入 */
|
||||
name: string
|
||||
name: string;
|
||||
/** 数据类型 选择 */
|
||||
type: string
|
||||
type: string;
|
||||
/** 默认值 输入 */
|
||||
value: string
|
||||
}
|
||||
value: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 数据模型
|
||||
*/
|
||||
export interface VisualEditorModel {
|
||||
/** 数据源名称 */
|
||||
name: string
|
||||
name: string;
|
||||
/** 绑定的字段 该字段创建的时候生成 */
|
||||
key: string
|
||||
key: string;
|
||||
/** 实体集合 */
|
||||
entitys: EntityType[]
|
||||
entitys: EntityType[];
|
||||
}
|
||||
/**
|
||||
* @description 接口请求对象
|
||||
*/
|
||||
export interface FetchApiItem {
|
||||
/** 随机生成的key */
|
||||
key: string
|
||||
key: string;
|
||||
/** 随机生成的key */
|
||||
name: string
|
||||
name: string;
|
||||
options: {
|
||||
/** 请求的url */
|
||||
url: string
|
||||
url: string;
|
||||
/** 请求的方法 */
|
||||
method: keyof typeof RequestEnum
|
||||
method: keyof typeof RequestEnum;
|
||||
/** 请求的内容类型 */
|
||||
contentType: keyof typeof ContentTypeEnum
|
||||
}
|
||||
contentType: keyof typeof ContentTypeEnum;
|
||||
};
|
||||
data: {
|
||||
/** 请求绑定对应的某个实体 */
|
||||
bind: string
|
||||
bind: string;
|
||||
/** 响应的结果绑定到某个实体上 */
|
||||
recv: string
|
||||
}
|
||||
recv: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -149,81 +151,81 @@ export interface FetchApiItem {
|
|||
*/
|
||||
export interface VisualEditorActions {
|
||||
fetch: {
|
||||
name: '接口请求'
|
||||
apis: FetchApiItem[]
|
||||
}
|
||||
name: '接口请求';
|
||||
apis: FetchApiItem[];
|
||||
};
|
||||
dialog: {
|
||||
name: '对话框'
|
||||
handlers: []
|
||||
}
|
||||
name: '对话框';
|
||||
handlers: [];
|
||||
};
|
||||
}
|
||||
/**
|
||||
* @description 总的数据集
|
||||
*/
|
||||
export interface VisualEditorModelValue {
|
||||
/** 页面 */
|
||||
pages: VisualEditorPages
|
||||
pages: VisualEditorPages;
|
||||
/** 实体 */
|
||||
models: VisualEditorModel[]
|
||||
models: VisualEditorModel[];
|
||||
/** 动作 */
|
||||
actions: VisualEditorActions
|
||||
actions: VisualEditorActions;
|
||||
}
|
||||
/**
|
||||
* @description 动画项
|
||||
*/
|
||||
export interface Animation {
|
||||
/** 动画名称 */
|
||||
label: string
|
||||
label: string;
|
||||
/** 动画类名 */
|
||||
value: string
|
||||
value: string;
|
||||
/** 动画持续时间 */
|
||||
duration: number
|
||||
duration: number;
|
||||
/** 动画延迟多久执行 */
|
||||
delay: number
|
||||
delay: number;
|
||||
/** 动画执行次数 */
|
||||
count: number
|
||||
count: number;
|
||||
/** 是否无限循环动画 */
|
||||
infinite: boolean
|
||||
infinite: boolean;
|
||||
}
|
||||
/**
|
||||
* @description 单个组件注册规则
|
||||
*/
|
||||
export interface VisualEditorComponent {
|
||||
/** 组件name */
|
||||
key: string
|
||||
key: string;
|
||||
/** 组件所属模块名称 */
|
||||
moduleName: keyof ComponentModules
|
||||
moduleName: keyof ComponentModules;
|
||||
/** 组件唯一id */
|
||||
_vid?: string
|
||||
_vid?: string;
|
||||
/** 组件中文名称 */
|
||||
label: string
|
||||
label: string;
|
||||
/** 组件预览函数 */
|
||||
preview: () => JSX.Element
|
||||
preview: () => JSX.Element;
|
||||
/** 组件渲染函数 */
|
||||
render: (data: {
|
||||
props: any
|
||||
model: any
|
||||
styles: CSSProperties
|
||||
block: VisualEditorBlockData
|
||||
custom: Record<string, any>
|
||||
}) => () => JSX.Element
|
||||
props: any;
|
||||
model: any;
|
||||
styles: CSSProperties;
|
||||
block: VisualEditorBlockData;
|
||||
custom: Record<string, any>;
|
||||
}) => () => JSX.Element;
|
||||
/** 组件是否可以被拖拽 */
|
||||
draggable?: boolean
|
||||
draggable?: boolean;
|
||||
/** 是否显示组件的样式配置项 */
|
||||
showStyleConfig?: boolean
|
||||
showStyleConfig?: boolean;
|
||||
/** 组件属性 */
|
||||
props?: Record<string, VisualEditorProps>
|
||||
props?: Record<string, VisualEditorProps>;
|
||||
/** 动画集 */
|
||||
animations?: Animation[]
|
||||
animations?: Animation[];
|
||||
/** 组件事件集合 */
|
||||
events?: { label: string; value: string }[]
|
||||
events?: { label: string; value: string }[];
|
||||
/** 组件样式 */
|
||||
styles?: CSSProperties
|
||||
styles?: CSSProperties;
|
||||
}
|
||||
|
||||
export interface VisualEditorMarkLines {
|
||||
x: { left: number; showLeft: number }[]
|
||||
y: { top: number; showTop: number }[]
|
||||
x: { left: number; showLeft: number }[];
|
||||
y: { top: number; showTop: number }[];
|
||||
}
|
||||
|
||||
export function createNewBlock(component: VisualEditorComponent): VisualEditorBlockData {
|
||||
|
@ -241,54 +243,54 @@ export function createNewBlock(component: VisualEditorComponent): VisualEditorBl
|
|||
paddingRight: '0',
|
||||
paddingLeft: '0',
|
||||
paddingBottom: '0',
|
||||
tempPadding: '0'
|
||||
tempPadding: '0',
|
||||
},
|
||||
hasResize: false,
|
||||
props: Object.keys(component.props || {}).reduce((prev, curr) => {
|
||||
const { propObj, prop } = useDotProp(prev, curr)
|
||||
const { propObj, prop } = useDotProp(prev, curr);
|
||||
if (component.props![curr]?.defaultValue) {
|
||||
propObj[prop] = prev[curr] = component.props![curr]?.defaultValue
|
||||
propObj[prop] = prev[curr] = component.props![curr]?.defaultValue;
|
||||
}
|
||||
return prev
|
||||
return prev;
|
||||
}, {}),
|
||||
draggable: component.draggable ?? true, // 是否可以拖拽
|
||||
showStyleConfig: component.showStyleConfig ?? true, // 是否显示组件样式配置
|
||||
animations: [], // 动画集
|
||||
actions: [], // 动作集合
|
||||
events: component.events || [], // 事件集合
|
||||
model: {}
|
||||
}
|
||||
model: {},
|
||||
};
|
||||
}
|
||||
|
||||
export interface VisualDragEvent {
|
||||
dragstart: {
|
||||
on: (cb: () => void) => void
|
||||
off: (cb: () => void) => void
|
||||
emit: () => void
|
||||
}
|
||||
on: (cb: () => void) => void;
|
||||
off: (cb: () => void) => void;
|
||||
emit: () => void;
|
||||
};
|
||||
dragend: {
|
||||
on: (cb: () => void) => void
|
||||
off: (cb: () => void) => void
|
||||
emit: () => void
|
||||
}
|
||||
on: (cb: () => void) => void;
|
||||
off: (cb: () => void) => void;
|
||||
emit: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
export const VisualDragProvider = (() => {
|
||||
const VISUAL_DRAG_PROVIDER = '@@VISUAL_DRAG_PROVIDER'
|
||||
const VISUAL_DRAG_PROVIDER = '@@VISUAL_DRAG_PROVIDER';
|
||||
return {
|
||||
provide: (data: VisualDragEvent) => {
|
||||
provide(VISUAL_DRAG_PROVIDER, data)
|
||||
provide(VISUAL_DRAG_PROVIDER, data);
|
||||
},
|
||||
inject: () => {
|
||||
return inject(VISUAL_DRAG_PROVIDER) as VisualDragEvent
|
||||
}
|
||||
}
|
||||
})()
|
||||
return inject(VISUAL_DRAG_PROVIDER) as VisualDragEvent;
|
||||
},
|
||||
};
|
||||
})();
|
||||
|
||||
// 组件模块
|
||||
export interface ComponentModules {
|
||||
baseWidgets: VisualEditorComponent[] // 基础组件
|
||||
containerComponents: VisualEditorComponent[] // 容器组件
|
||||
baseWidgets: VisualEditorComponent[]; // 基础组件
|
||||
containerComponents: VisualEditorComponent[]; // 容器组件
|
||||
}
|
||||
/**
|
||||
* @description 创建编辑器配置
|
||||
|
@ -297,40 +299,40 @@ export interface ComponentModules {
|
|||
export function createVisualEditorConfig() {
|
||||
const componentModules: ComponentModules = {
|
||||
baseWidgets: [],
|
||||
containerComponents: []
|
||||
}
|
||||
containerComponents: [],
|
||||
};
|
||||
// const componentList: VisualEditorComponent[] = []
|
||||
const componentMap: Record<string, VisualEditorComponent> = {}
|
||||
const componentMap: Record<string, VisualEditorComponent> = {};
|
||||
return {
|
||||
componentModules,
|
||||
componentMap,
|
||||
registry: <
|
||||
_,
|
||||
Props extends Record<string, VisualEditorProps> = {},
|
||||
Model extends Record<string, string> = {}
|
||||
Model extends Record<string, string> = {},
|
||||
>(
|
||||
moduleName: keyof ComponentModules,
|
||||
key: string,
|
||||
component: {
|
||||
label: string
|
||||
preview: () => JSX.Element
|
||||
label: string;
|
||||
preview: () => JSX.Element;
|
||||
render: (data: {
|
||||
props: { [k in keyof Props]: any }
|
||||
model: Partial<{ [k in keyof Model]: any }>
|
||||
styles: CSSProperties
|
||||
block: VisualEditorBlockData
|
||||
custom: Record<string, any>
|
||||
}) => () => JSX.Element
|
||||
props?: Props
|
||||
model?: Model
|
||||
styles?: CSSProperties
|
||||
}
|
||||
props: { [k in keyof Props]: any };
|
||||
model: Partial<{ [k in keyof Model]: any }>;
|
||||
styles: CSSProperties;
|
||||
block: VisualEditorBlockData;
|
||||
custom: Record<string, any>;
|
||||
}) => () => JSX.Element;
|
||||
props?: Props;
|
||||
model?: Model;
|
||||
styles?: CSSProperties;
|
||||
},
|
||||
) => {
|
||||
const comp = { ...component, key, moduleName }
|
||||
componentModules[moduleName].push(comp)
|
||||
componentMap[key] = comp
|
||||
}
|
||||
}
|
||||
const comp = { ...component, key, moduleName };
|
||||
componentModules[moduleName].push(comp);
|
||||
componentMap[key] = comp;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export type VisualEditorConfig = ReturnType<typeof createVisualEditorConfig>
|
||||
export type VisualEditorConfig = ReturnType<typeof createVisualEditorConfig>;
|
||||
|
|
|
@ -1,25 +1,38 @@
|
|||
module.exports = {
|
||||
root: true,
|
||||
plugins: ['stylelint-order', 'stylelint-scss'],
|
||||
plugins: ['stylelint-order'],
|
||||
customSyntax: 'postcss-html',
|
||||
extends: ['stylelint-config-standard', 'stylelint-config-prettier'],
|
||||
rules: {
|
||||
'selector-class-pattern': null,
|
||||
'selector-pseudo-class-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignorePseudoClasses: ['global', 'v-deep', 'deep']
|
||||
}
|
||||
ignorePseudoClasses: ['global'],
|
||||
},
|
||||
],
|
||||
'selector-pseudo-element-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignorePseudoElements: ['v-deep', 'deep']
|
||||
}
|
||||
ignorePseudoElements: ['v-deep'],
|
||||
},
|
||||
],
|
||||
'at-rule-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignoreAtRules: ['function', 'if', 'each', 'include', 'mixin', 'for']
|
||||
}
|
||||
ignoreAtRules: [
|
||||
'tailwind',
|
||||
'apply',
|
||||
'variants',
|
||||
'responsive',
|
||||
'screen',
|
||||
'function',
|
||||
'if',
|
||||
'each',
|
||||
'include',
|
||||
'mixin',
|
||||
],
|
||||
},
|
||||
],
|
||||
'no-empty-source': null,
|
||||
'named-grid-areas-no-invalid': null,
|
||||
|
@ -28,12 +41,12 @@ module.exports = {
|
|||
'font-family-no-missing-generic-family-keyword': null,
|
||||
'declaration-colon-space-after': 'always-single-line',
|
||||
'declaration-colon-space-before': 'never',
|
||||
'declaration-block-trailing-semicolon': ['always', { ignore: ['single-declaration'] }],
|
||||
// 'declaration-block-trailing-semicolon': 'always',
|
||||
'rule-empty-line-before': [
|
||||
'always',
|
||||
{
|
||||
ignore: ['after-comment', 'first-nested']
|
||||
}
|
||||
ignore: ['after-comment', 'first-nested'],
|
||||
},
|
||||
],
|
||||
'unit-no-unknown': [true, { ignoreUnits: ['rpx'] }],
|
||||
'order/order': [
|
||||
|
@ -44,165 +57,37 @@ module.exports = {
|
|||
'declarations',
|
||||
{
|
||||
type: 'at-rule',
|
||||
name: 'supports'
|
||||
name: 'supports',
|
||||
},
|
||||
{
|
||||
type: 'at-rule',
|
||||
name: 'media'
|
||||
name: 'media',
|
||||
},
|
||||
'rules'
|
||||
'rules',
|
||||
],
|
||||
{ severity: 'warning' }
|
||||
{ severity: 'warning' },
|
||||
],
|
||||
// Specify the alphabetical order of the attributes in the declaration block
|
||||
'order/properties-order': [
|
||||
'position',
|
||||
'top',
|
||||
'right',
|
||||
'bottom',
|
||||
'left',
|
||||
'z-index',
|
||||
'display',
|
||||
'float',
|
||||
'width',
|
||||
'height',
|
||||
'max-width',
|
||||
'max-height',
|
||||
'min-width',
|
||||
'min-height',
|
||||
'padding',
|
||||
'padding-top',
|
||||
'padding-right',
|
||||
'padding-bottom',
|
||||
'padding-left',
|
||||
'margin',
|
||||
'margin-top',
|
||||
'margin-right',
|
||||
'margin-bottom',
|
||||
'margin-left',
|
||||
'margin-collapse',
|
||||
'margin-top-collapse',
|
||||
'margin-right-collapse',
|
||||
'margin-bottom-collapse',
|
||||
'margin-left-collapse',
|
||||
'overflow',
|
||||
'overflow-x',
|
||||
'overflow-y',
|
||||
'clip',
|
||||
'clear',
|
||||
'font',
|
||||
'font-family',
|
||||
'font-size',
|
||||
'font-smoothing',
|
||||
'osx-font-smoothing',
|
||||
'font-style',
|
||||
'font-weight',
|
||||
'hyphens',
|
||||
'src',
|
||||
'line-height',
|
||||
'letter-spacing',
|
||||
'word-spacing',
|
||||
'color',
|
||||
'text-align',
|
||||
'text-decoration',
|
||||
'text-indent',
|
||||
'text-overflow',
|
||||
'text-rendering',
|
||||
'text-size-adjust',
|
||||
'text-shadow',
|
||||
'text-transform',
|
||||
'word-break',
|
||||
'word-wrap',
|
||||
'white-space',
|
||||
'vertical-align',
|
||||
'list-style',
|
||||
'list-style-type',
|
||||
'list-style-position',
|
||||
'list-style-image',
|
||||
'pointer-events',
|
||||
'cursor',
|
||||
'background',
|
||||
'background-attachment',
|
||||
'background-color',
|
||||
'background-image',
|
||||
'background-position',
|
||||
'background-repeat',
|
||||
'background-size',
|
||||
'border',
|
||||
'border-collapse',
|
||||
'border-top',
|
||||
'border-right',
|
||||
'border-bottom',
|
||||
'border-left',
|
||||
'border-color',
|
||||
'border-image',
|
||||
'border-top-color',
|
||||
'border-right-color',
|
||||
'border-bottom-color',
|
||||
'border-left-color',
|
||||
'border-spacing',
|
||||
'border-style',
|
||||
'border-top-style',
|
||||
'border-right-style',
|
||||
'border-bottom-style',
|
||||
'border-left-style',
|
||||
'border-width',
|
||||
'border-top-width',
|
||||
'border-right-width',
|
||||
'border-bottom-width',
|
||||
'border-left-width',
|
||||
'border-radius',
|
||||
'border-top-right-radius',
|
||||
'border-bottom-right-radius',
|
||||
'border-bottom-left-radius',
|
||||
'border-top-left-radius',
|
||||
'border-radius-topright',
|
||||
'border-radius-bottomright',
|
||||
'border-radius-bottomleft',
|
||||
'border-radius-topleft',
|
||||
'content',
|
||||
'quotes',
|
||||
'outline',
|
||||
'outline-offset',
|
||||
'opacity',
|
||||
'filter',
|
||||
'visibility',
|
||||
'size',
|
||||
'zoom',
|
||||
'transform',
|
||||
'box-align',
|
||||
'box-flex',
|
||||
'box-orient',
|
||||
'box-pack',
|
||||
'box-shadow',
|
||||
'box-sizing',
|
||||
'table-layout',
|
||||
'animation',
|
||||
'animation-delay',
|
||||
'animation-duration',
|
||||
'animation-iteration-count',
|
||||
'animation-name',
|
||||
'animation-play-state',
|
||||
'animation-timing-function',
|
||||
'animation-fill-mode',
|
||||
'transition',
|
||||
'transition-delay',
|
||||
'transition-duration',
|
||||
'transition-property',
|
||||
'transition-timing-function',
|
||||
'background-clip',
|
||||
'backface-visibility',
|
||||
'resize',
|
||||
'appearance',
|
||||
'user-select',
|
||||
'interpolation-mode',
|
||||
'direction',
|
||||
'marks',
|
||||
'page',
|
||||
'set-link-source',
|
||||
'unicode-bidi',
|
||||
'speak'
|
||||
]
|
||||
},
|
||||
ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts']
|
||||
}
|
||||
ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.vue', '**/*.vue', '*.html', '**/*.html'],
|
||||
extends: ['stylelint-config-recommended', 'stylelint-config-html'],
|
||||
rules: {
|
||||
'keyframes-name-pattern': null,
|
||||
'selector-pseudo-class-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignorePseudoClasses: ['deep', 'global'],
|
||||
},
|
||||
],
|
||||
'selector-pseudo-element-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignorePseudoElements: ['v-deep', 'v-global', 'v-slotted'],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
@ -24,12 +24,26 @@
|
|||
"removeComments": false,
|
||||
"strictFunctionTypes": false,
|
||||
"baseUrl": ".",
|
||||
"types": ["vite/client", "node"],
|
||||
"typeRoots": ["./node_modules/@types/", "./types"],
|
||||
"types": [
|
||||
"vite/client",
|
||||
"node",
|
||||
"element-plus/global"
|
||||
],
|
||||
"typeRoots": [
|
||||
"./node_modules/@types/",
|
||||
"./types"
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": ["esnext", "dom", "dom.iterable", "scripthost"]
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.ts",
|
||||
|
@ -43,5 +57,8 @@
|
|||
"preview/**/*.tsx",
|
||||
"preview/**/*.vue"
|
||||
],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
/** 网站标题 */
|
||||
readonly VITE_APP_TITLE: string;
|
||||
/** 网站部署的目录 */
|
||||
readonly VITE_BASE_URL: string;
|
||||
/** API 接口路径 */
|
||||
readonly VITE_BASE_API: string;
|
||||
/** socket 请求路径前缀 */
|
||||
readonly VITE_BASE_SOCKET_PATH: string;
|
||||
/** socket 命名空间 */
|
||||
readonly VITE_BASE_SOCKET_NSP: string;
|
||||
/** mock API 路径 */
|
||||
readonly VITE_MOCK_API: string;
|
||||
// 更多环境变量...
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
import type {
|
||||
ComponentRenderProxy,
|
||||
VNode,
|
||||
VNodeChild,
|
||||
ComponentPublicInstance,
|
||||
FunctionalComponent,
|
||||
PropType as VuePropType,
|
||||
} from 'vue';
|
||||
|
||||
declare global {
|
||||
const __APP_INFO__: {
|
||||
pkg: {
|
||||
name: string;
|
||||
version: string;
|
||||
dependencies: Recordable<string>;
|
||||
devDependencies: Recordable<string>;
|
||||
};
|
||||
lastBuildTime: string;
|
||||
};
|
||||
// declare interface Window {
|
||||
// // Global vue app instance
|
||||
// __APP__: App<Element>;
|
||||
// }
|
||||
|
||||
// vue
|
||||
declare type PropType<T> = VuePropType<T>;
|
||||
declare type VueNode = VNodeChild | JSX.Element;
|
||||
|
||||
export type Writable<T> = {
|
||||
-readonly [P in keyof T]: T[P];
|
||||
};
|
||||
type RemoveIndex<T> = {
|
||||
[K in keyof T as string extends K ? never : number extends K ? never : K]: T[K];
|
||||
};
|
||||
declare type Nullable<T> = T | null;
|
||||
declare type NonNullable<T> = T extends null | undefined ? never : T;
|
||||
declare type Recordable<T = any> = Record<string, T>;
|
||||
declare type ReadonlyRecordable<T = any> = {
|
||||
readonly [key: string]: T;
|
||||
};
|
||||
declare type Indexable<T = any> = {
|
||||
[key: string]: T;
|
||||
};
|
||||
declare type DeepPartial<T> = {
|
||||
[P in keyof T]?: DeepPartial<T[P]>;
|
||||
};
|
||||
|
||||
declare type TimeoutHandle = ReturnType<typeof setTimeout>;
|
||||
declare type IntervalHandle = ReturnType<typeof setInterval>;
|
||||
|
||||
declare interface ChangeEvent extends Event {
|
||||
target: HTMLInputElement;
|
||||
}
|
||||
|
||||
declare interface WheelEvent {
|
||||
path?: EventTarget[];
|
||||
}
|
||||
declare function parseInt(s: string | number, radix?: number): number;
|
||||
|
||||
declare function parseFloat(string: string | number): number;
|
||||
|
||||
namespace JSX {
|
||||
// tslint:disable no-empty-interface
|
||||
type Element = VNode;
|
||||
// tslint:disable no-empty-interface
|
||||
type ElementClass = ComponentRenderProxy;
|
||||
interface ElementAttributesProperty {
|
||||
$props: any;
|
||||
}
|
||||
interface IntrinsicElements {
|
||||
[elem: string]: any;
|
||||
}
|
||||
interface IntrinsicAttributes {
|
||||
[elem: string]: any;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'vue' {
|
||||
export type JSXComponent<Props = any> =
|
||||
| { new (): ComponentPublicInstance<Props> }
|
||||
| FunctionalComponent<Props>;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
declare interface Fn<T = any, R = T> {
|
||||
(...arg: T[]): R;
|
||||
}
|
||||
|
||||
declare interface PromiseFn<T = any, R = T> {
|
||||
(...arg: T[]): Promise<R>;
|
||||
}
|
||||
|
||||
declare type RefType<T> = T | null;
|
||||
|
||||
declare type LabelValueOptions = {
|
||||
label: string;
|
||||
value: any;
|
||||
[key: string]: string | number | boolean;
|
||||
}[];
|
||||
|
||||
declare type EmitType = (event: string, ...args: any[]) => void;
|
||||
|
||||
declare type TargetContext = '_self' | '_blank';
|
||||
|
||||
declare interface ComponentElRef<T extends HTMLElement = HTMLDivElement> {
|
||||
$el: T;
|
||||
}
|
||||
|
||||
/** 将联合类型转为交叉类型 */
|
||||
declare type UnionToIntersection<U> = (U extends any ? (k: U) => void : never) extends (
|
||||
k: infer I,
|
||||
) => void
|
||||
? I
|
||||
: never;
|
||||
|
||||
declare type ComponentRef<T extends HTMLElement = HTMLDivElement> = ComponentElRef<T> | null;
|
||||
|
||||
declare type ElRef<T extends HTMLElement = HTMLDivElement> = Nullable<T>;
|
|
@ -0,0 +1,20 @@
|
|||
declare module '*.vue' {
|
||||
import { DefineComponent } from 'vue';
|
||||
const Component: DefineComponent<{}, {}, any>;
|
||||
export default Component;
|
||||
}
|
||||
|
||||
declare module 'mitt' {
|
||||
import mitt from 'mitt';
|
||||
export default mitt;
|
||||
}
|
||||
|
||||
declare module 'blueimp-md5' {
|
||||
import md5 from 'blueimp-md5';
|
||||
export default md5;
|
||||
}
|
||||
|
||||
declare module 'virtual:*' {
|
||||
const result: any;
|
||||
export default result;
|
||||
}
|
|
@ -1,67 +1,67 @@
|
|||
declare module '*.bmp' {
|
||||
const src: string
|
||||
export default src
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.gif' {
|
||||
const src: string
|
||||
export default src
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.jpg' {
|
||||
const src: string
|
||||
export default src
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.jpeg' {
|
||||
const src: string
|
||||
export default src
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.png' {
|
||||
const src: string
|
||||
export default src
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.webp' {
|
||||
const src: string
|
||||
export default src
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.module.css' {
|
||||
const classes: { readonly [key: string]: string }
|
||||
export default classes
|
||||
const classes: { readonly [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
|
||||
declare module '*.module.scss' {
|
||||
const classes: { readonly [key: string]: string }
|
||||
export default classes
|
||||
const classes: { readonly [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
|
||||
declare module '*.module.less' {
|
||||
const classes: { readonly [key: string]: string }
|
||||
export default classes
|
||||
const classes: { readonly [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
|
||||
declare module '*.module.sass' {
|
||||
const classes: { readonly [key: string]: string }
|
||||
export default classes
|
||||
const classes: { readonly [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
|
||||
declare module 'moment/locale/*' {
|
||||
const LocaleMessage: { [key: string]: any }
|
||||
export default LocaleMessage
|
||||
const LocaleMessage: { [key: string]: any };
|
||||
export default LocaleMessage;
|
||||
}
|
||||
|
||||
declare module 'ant-design-vue/es/locale-provider/*' {
|
||||
const LocaleMessage: { [key: string]: any }
|
||||
export default LocaleMessage
|
||||
const LocaleMessage: { [key: string]: any };
|
||||
export default LocaleMessage;
|
||||
}
|
||||
|
||||
// ant-design-vue/es/base
|
||||
declare module 'ant-design-vue/es/base' {
|
||||
class Base {
|
||||
static install(vue: typeof Vue): void
|
||||
static install(vue: typeof Vue): void;
|
||||
}
|
||||
export default Base
|
||||
export default Base;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
declare module '*.vue' {
|
||||
import { defineComponent } from 'vue'
|
||||
const component: ReturnType<typeof defineComponent>
|
||||
export default component
|
||||
import { defineComponent } from 'vue';
|
||||
const component: ReturnType<typeof defineComponent>;
|
||||
export default component;
|
||||
}
|
||||
|
||||
// declare module '*.vue' {
|
||||
|
@ -10,15 +10,16 @@ declare module '*.vue' {
|
|||
// }
|
||||
|
||||
// declare module '@vue/runtime-core' {
|
||||
// interface ComponentCustomProperties {
|
||||
// $createLoading: () => any
|
||||
// }
|
||||
// export interface ComponentCustomProperties {
|
||||
// $http: typeof axios
|
||||
// $validate: (data: object, rule: object) => boolean
|
||||
// }
|
||||
// }
|
||||
|
||||
declare type Nullable<T> = T | null
|
||||
declare type Nullable<T> = T | null;
|
||||
|
||||
declare type CustomizedHTMLElement<T> = HTMLElement & T
|
||||
declare type CustomizedHTMLElement<T> = HTMLElement & T;
|
||||
|
||||
declare type Indexable<T> = {
|
||||
[key: string]: T
|
||||
}
|
||||
[key: string]: T;
|
||||
};
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import { type PermissionType } from '@/core/permission/modules/types';
|
||||
|
||||
interface Title18n {
|
||||
zh_CN: string;
|
||||
en_US: string;
|
||||
}
|
||||
|
||||
declare module 'vue-router' {
|
||||
interface RouteMeta extends Record<string | number | symbol, unknown> {
|
||||
/** 标题 */
|
||||
title: string | Title18n;
|
||||
/** 当前路由是否需要权限验证 */
|
||||
isAuth?: boolean;
|
||||
/** 当前路由权限 */
|
||||
perms?: PermissionType[];
|
||||
/** 是否需要缓存 */
|
||||
keepAlive?: boolean;
|
||||
/** 当前路由namePath 祖先name集合 */
|
||||
namePath?: string[];
|
||||
/** 当前路由所在的完整路径 */
|
||||
fullPath?: string;
|
||||
/** 是否固定在标签栏 */
|
||||
affix?: boolean;
|
||||
/** 菜单图标 */
|
||||
icon?: string;
|
||||
/** 当前页面切换动画 */
|
||||
transitionName?: string;
|
||||
/** 不在菜单中显示 */
|
||||
hideInMenu?: boolean;
|
||||
/** 设置当前路由高亮的菜单项,值为route fullPath或route name,一般用于详情页 */
|
||||
activeMenu?: string;
|
||||
isLink?: boolean;
|
||||
}
|
||||
}
|
|
@ -1,16 +1,14 @@
|
|||
import { ComponentCustomProperties } from 'vue'
|
||||
import { Store } from 'vuex'
|
||||
// import { ComponentCustomProperties } from 'vue';
|
||||
import { Store } from 'vuex';
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
// declare your own store states
|
||||
interface State {
|
||||
count: number
|
||||
count: number;
|
||||
}
|
||||
|
||||
// provide typings for `this.$store`
|
||||
interface ComponentCustomProperties {
|
||||
$store: Store<State>
|
||||
$store: Store<State>;
|
||||
}
|
||||
}
|
||||
|
||||
export {}
|
||||
|
|
125
vite.config.ts
125
vite.config.ts
|
@ -1,59 +1,91 @@
|
|||
import { ConfigEnv, loadEnv, UserConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||
import legacy from '@vitejs/plugin-legacy'
|
||||
import { resolve } from 'path'
|
||||
import ViteComponents, { ElementPlusResolver, VantResolver } from 'vite-plugin-components'
|
||||
import WindiCSS from 'vite-plugin-windicss'
|
||||
import VitePluginElementPlus from 'vite-plugin-element-plus'
|
||||
import { ConfigEnv, loadEnv, UserConfig } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx';
|
||||
import legacy from '@vitejs/plugin-legacy';
|
||||
import { resolve } from 'path';
|
||||
import { ElementPlusResolver, VantResolver } from 'unplugin-vue-components/resolvers';
|
||||
import AutoImport from 'unplugin-auto-import/vite';
|
||||
import Components from 'unplugin-vue-components/vite';
|
||||
import WindiCSS from 'vite-plugin-windicss';
|
||||
|
||||
const CWD = process.cwd()
|
||||
const CWD = process.cwd();
|
||||
|
||||
const prefix = `monaco-editor/esm/vs`
|
||||
const prefix = `monaco-editor/esm/vs`;
|
||||
|
||||
// https://cn.vitejs.dev/config/
|
||||
export default ({ mode }: ConfigEnv): UserConfig => {
|
||||
// 环境变量
|
||||
const { VITE_BASE_URL } = loadEnv(mode, CWD)
|
||||
const { VITE_BASE_URL } = loadEnv(mode, CWD);
|
||||
return {
|
||||
base: VITE_BASE_URL, // 设置打包路径
|
||||
css: {
|
||||
modules: {
|
||||
localsConvention: 'camelCase' // 默认只支持驼峰,修改为同时支持横线和驼峰
|
||||
}
|
||||
localsConvention: 'camelCase', // 默认只支持驼峰,修改为同时支持横线和驼峰
|
||||
},
|
||||
preprocessorOptions: {
|
||||
scss: {
|
||||
charset: false,
|
||||
},
|
||||
less: {
|
||||
charset: false,
|
||||
},
|
||||
},
|
||||
// TODO 构建包含@charset问题 https://github.com/vitejs/vite/issues/5833
|
||||
charset: false,
|
||||
postcss: {
|
||||
plugins: [
|
||||
{
|
||||
postcssPlugin: 'internal:charset-removal',
|
||||
AtRule: {
|
||||
charset: (atRule) => {
|
||||
if (atRule.name === 'charset') {
|
||||
atRule.remove();
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
vue(),
|
||||
vueJsx(),
|
||||
WindiCSS(),
|
||||
legacy({
|
||||
targets: ['defaults', 'not IE 11']
|
||||
targets: ['defaults', 'not IE 11'],
|
||||
}),
|
||||
ViteComponents({
|
||||
globalComponentsDeclaration: true,
|
||||
// 自动导入组件(还不够完善,可能会有样式丢失)
|
||||
// valid file extensions for components.
|
||||
extensions: ['vue', 'tsx', 'js'],
|
||||
customComponentResolvers: [ElementPlusResolver(), VantResolver()]
|
||||
AutoImport({
|
||||
resolvers: [ElementPlusResolver(), VantResolver()],
|
||||
}),
|
||||
Components({
|
||||
resolvers: [ElementPlusResolver(), VantResolver()],
|
||||
}),
|
||||
VitePluginElementPlus({
|
||||
// 如果你需要使用 [component name].scss 源文件,你需要把下面的注释取消掉。
|
||||
// 对于所有的 API 你可以参考 https://github.com/element-plus/vite-plugin-element-plus
|
||||
// 的文档注释
|
||||
// useSource: true
|
||||
format: mode === 'development' ? 'esm' : 'cjs'
|
||||
})
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': resolve(__dirname, 'src') // 设置 `@` 指向 `src` 目录
|
||||
}
|
||||
alias: [
|
||||
{
|
||||
find: '@',
|
||||
replacement: resolve(__dirname, './src'),
|
||||
},
|
||||
],
|
||||
},
|
||||
build: {
|
||||
cssCodeSplit: true, // 如果设置为false,整个项目中的所有 CSS 将被提取到一个 CSS 文件中
|
||||
sourcemap: false, // 构建后是否生成 source map 文件。如果为 true,将会创建一个独立的 source map 文件
|
||||
target: 'modules', // 设置最终构建的浏览器兼容目标。默认值是一个 Vite 特有的值——'modules' 还可设置为 'es2015' 'es2016'等
|
||||
chunkSizeWarningLimit: 550, // 单位kb 打包后文件大小警告的限制 (文件大于此此值会出现警告)
|
||||
assetsInlineLimit: 4096, // 单位字节(1024等于1kb) 小于此阈值的导入或引用资源将内联为 base64 编码,以避免额外的 http 请求。设置为 0 可以完全禁用此项。
|
||||
minify: 'terser', // 'terser' 相对较慢,但大多数情况下构建后的文件体积更小。'esbuild' 最小化混淆更快但构建后的文件相对更大。
|
||||
terserOptions: {
|
||||
compress: {
|
||||
drop_console: true, // 生产环境去除console
|
||||
drop_debugger: true, // 生产环境去除debugger
|
||||
},
|
||||
},
|
||||
rollupOptions: {
|
||||
input: {
|
||||
main: resolve(__dirname, 'index.html'),
|
||||
preview: resolve(__dirname, 'preview/index.html')
|
||||
preview: resolve(__dirname, 'preview/index.html'),
|
||||
},
|
||||
output: {
|
||||
manualChunks: {
|
||||
|
@ -61,22 +93,13 @@ export default ({ mode }: ConfigEnv): UserConfig => {
|
|||
cssWorker: [`${prefix}/language/css/css.worker`],
|
||||
htmlWorker: [`${prefix}/language/html/html.worker`],
|
||||
tsWorker: [`${prefix}/language/typescript/ts.worker`],
|
||||
editorWorker: [`${prefix}/editor/editor.worker`]
|
||||
}
|
||||
}
|
||||
}
|
||||
editorWorker: [`${prefix}/editor/editor.worker`],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
optimizeDeps: {
|
||||
include: [
|
||||
'vue',
|
||||
'vue-router',
|
||||
'@vueuse/core',
|
||||
'element-plus',
|
||||
'vant',
|
||||
'lodash',
|
||||
'vuedraggable'
|
||||
],
|
||||
exclude: ['vue-demi']
|
||||
include: ['@vueuse/core', 'element-plus', 'vant', 'lodash', 'vuedraggable'],
|
||||
},
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
|
@ -90,9 +113,9 @@ export default ({ mode }: ConfigEnv): UserConfig => {
|
|||
target: 'http://29135jo738.zicp.vip/api/v1',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
rewrite: (path) => path.replace('/api/', '/')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
rewrite: (path) => path.replace('/api/', '/'),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue