first commit
This commit is contained in:
commit
4685718dbc
|
@ -0,0 +1,11 @@
|
|||
# Editor configuration, see http://editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
|
@ -0,0 +1,48 @@
|
|||
VITE_BASE_URL=/
|
||||
|
||||
VITE_APP_TITLE=ERP 综合办公系统
|
||||
|
||||
VITE_APP_DESC=
|
||||
|
||||
# the prefix of the icon name
|
||||
VITE_ICON_PREFIX=icon
|
||||
|
||||
# the prefix of the local svg icon component, must include VITE_ICON_PREFIX
|
||||
# format {VITE_ICON_PREFIX}-{local icon name}
|
||||
VITE_ICON_LOCAL_PREFIX=icon-local
|
||||
|
||||
# 路由模式: static 静态 | dynamic 动态
|
||||
VITE_AUTH_ROUTE_MODE=static
|
||||
|
||||
# static 静态路由首页地址
|
||||
VITE_ROUTE_HOME=home
|
||||
|
||||
# 默认菜单图标
|
||||
VITE_MENU_ICON=mdi:menu
|
||||
|
||||
# 当在开发模式下,是否使用代理
|
||||
VITE_HTTP_PROXY=Y
|
||||
|
||||
# vue-router 路由模式: hash | history | memory
|
||||
VITE_ROUTER_HISTORY_MODE=history
|
||||
|
||||
# success code of backend service, when the code is received, the request is successful
|
||||
VITE_SERVICE_SUCCESS_CODE=200
|
||||
|
||||
# logout codes of backend service, when the code is received, the user will be logged out and redirected to login page
|
||||
VITE_SERVICE_LOGOUT_CODES=8888,8889
|
||||
|
||||
# modal logout codes of backend service, when the code is received, the user will be logged out by displaying a modal
|
||||
VITE_SERVICE_MODAL_LOGOUT_CODES=7777,7778
|
||||
|
||||
# token expired codes of backend service, when the code is received, it will refresh the token and resend the request
|
||||
VITE_SERVICE_EXPIRED_TOKEN_CODES=9999,9998
|
||||
|
||||
# when the route mode is static, the defined super role
|
||||
VITE_STATIC_SUPER_ROLE=R_SUPER
|
||||
|
||||
# sourcemap
|
||||
VITE_SOURCE_MAP=N
|
||||
|
||||
# Used to differentiate storage across different domains
|
||||
VITE_STORAGE_PREFIX=app_
|
|
@ -0,0 +1,7 @@
|
|||
# backend service base url, prod environment
|
||||
VITE_SERVICE_BASE_URL=https://mock.apifox.com/m1/3109515-0-default
|
||||
|
||||
# other backend service base url, prod environment
|
||||
VITE_OTHER_SERVICE_BASE_URL= `{
|
||||
"demo": "http://localhost:9529"
|
||||
}`
|
|
@ -0,0 +1,7 @@
|
|||
# backend service base url, prod environment
|
||||
VITE_SERVICE_BASE_URL=http://192.168.147.238:8083
|
||||
|
||||
# other backend service base url, prod environment
|
||||
VITE_OTHER_SERVICE_BASE_URL= `{
|
||||
"demo": "http://localhost:9529"
|
||||
}`
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
extends: ['antfu'],
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
"*.vue" eol=lf
|
||||
"*.js" eol=lf
|
||||
"*.ts" eol=lf
|
||||
"*.jsx" eol=lf
|
||||
"*.tsx" eol=lf
|
||||
"*.mjs" eol=lf
|
||||
"*.json" eol=lf
|
||||
"*.html" eol=lf
|
||||
"*.css" eol=lf
|
||||
"*.scss" eol=lf
|
||||
"*.md" eol=lf
|
||||
"*.yaml" eol=lf
|
||||
"*.yml" eol=lf
|
|
@ -0,0 +1,36 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
!.vscode/settings.json
|
||||
!.vscode/launch.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
.history
|
||||
.VSCodeCounter
|
||||
vite.config.ts.**
|
|
@ -0,0 +1,4 @@
|
|||
registry=https://registry.npmmirror.com/
|
||||
shamefully-hoist=true
|
||||
ignore-workspace-root-check=true
|
||||
link-workspace-packages=true
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"recommendations": [
|
||||
// my extensions, ofc :P
|
||||
"antfu.browse-lite",
|
||||
"antfu.iconify",
|
||||
"antfu.slidev",
|
||||
"antfu.unocss",
|
||||
"antfu.vite",
|
||||
"antfu.where-am-i",
|
||||
"antfu.open-in-github-button",
|
||||
"lokalise.i18n-ally",
|
||||
// themes & icons
|
||||
"antfu.icons-carbon",
|
||||
"antfu.theme-vitesse",
|
||||
"file-icons.file-icons",
|
||||
"sainnhe.gruvbox-material",
|
||||
// life savers!
|
||||
"dbaeumer.vscode-eslint",
|
||||
"Vue.volar",
|
||||
"GitHub.copilot",
|
||||
"usernamehw.errorlens",
|
||||
"streetsidesoftware.code-spell-checker",
|
||||
// up to you
|
||||
"eamodio.gitlens",
|
||||
"EditorConfig.EditorConfig",
|
||||
"github.vscode-github-actions",
|
||||
"GitHub.vscode-pull-request-github",
|
||||
"johnsoncodehk.vscode-tsconfig-helper",
|
||||
"mpontus.tab-cycle",
|
||||
"naumovs.color-highlight",
|
||||
"WakaTime.vscode-wakatime",
|
||||
"znck.grammarly"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
{
|
||||
// ========== Visuals ==========
|
||||
"editor.cursorSmoothCaretAnimation": "on",
|
||||
// "editor.fontFamily": "Input Mono, monospace",
|
||||
"editor.guides.bracketPairs": "active",
|
||||
"editor.lineNumbers": "interval",
|
||||
"editor.renderWhitespace": "boundary",
|
||||
"window.autoDetectColorScheme": true,
|
||||
"workbench.colorTheme": "Vitesse Dark Soft",
|
||||
"workbench.editor.tabActionLocation": "left",
|
||||
"workbench.fontAliasing": "antialiased",
|
||||
"workbench.iconTheme": "file-icons-colourless",
|
||||
"workbench.list.smoothScrolling": true,
|
||||
"workbench.preferredDarkColorTheme": "Vitesse Dark",
|
||||
"workbench.preferredLightColorTheme": "Vitesse Dark Soft",
|
||||
"workbench.productIconTheme": "icons-carbon",
|
||||
"workbench.sideBar.location": "right",
|
||||
"workbench.startupEditor": "newUntitledFile",
|
||||
"workbench.tree.expandMode": "singleClick",
|
||||
"workbench.tree.indent": 10,
|
||||
// ========== Editor ==========
|
||||
"debug.onTaskErrors": "debugAnyway",
|
||||
"diffEditor.ignoreTrimWhitespace": true,
|
||||
"editor.wordSeparators": "`~!@#%^&*()=+[{]}\\|;:'\",.<>/?",
|
||||
"editor.find.addExtraSpaceOnTop": false,
|
||||
"editor.inlineSuggest.enabled": true,
|
||||
"editor.multiCursorModifier": "ctrlCmd",
|
||||
"editor.suggestSelection": "first",
|
||||
"editor.tabSize": 2,
|
||||
"editor.unicodeHighlight.invisibleCharacters": false,
|
||||
"editor.stickyScroll.enabled": true,
|
||||
"editor.hover.sticky": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": "never",
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
"explorer.confirmDelete": false,
|
||||
"explorer.confirmDragAndDrop": false,
|
||||
"files.eol": "\n",
|
||||
"files.insertFinalNewline": true,
|
||||
"files.simpleDialog.enable": true,
|
||||
"git.autofetch": true,
|
||||
"git.confirmSync": false,
|
||||
"git.enableSmartCommit": true,
|
||||
"git.untrackedChanges": "separate",
|
||||
"scm.diffDecorationsGutterWidth": 2,
|
||||
"terminal.integrated.cursorBlinking": true,
|
||||
"terminal.integrated.cursorStyle": "line",
|
||||
"terminal.integrated.fontWeight": "300",
|
||||
"terminal.integrated.persistentSessionReviveProcess": "never",
|
||||
"terminal.integrated.tabs.enabled": true,
|
||||
"workbench.editor.closeOnFileDelete": true,
|
||||
"workbench.editor.highlightModifiedTabs": true,
|
||||
"workbench.editor.limit.enabled": true,
|
||||
"workbench.editor.limit.perEditorGroup": true,
|
||||
"workbench.editor.limit.value": 5,
|
||||
"search.exclude": {
|
||||
"**/*.snap": true,
|
||||
"**/*.svg": true,
|
||||
"**/.git": true,
|
||||
"**/.github": false,
|
||||
"**/.nuxt": true,
|
||||
"**/.output": true,
|
||||
"**/.pnpm": true,
|
||||
"**/.vscode": true,
|
||||
"**/.yarn": true,
|
||||
"**/assets": true,
|
||||
"**/bower_components": true,
|
||||
"**/dist/**": true,
|
||||
"**/logs": true,
|
||||
"**/node_modules": true,
|
||||
"**/out/**": true,
|
||||
"**/package-lock.json": true,
|
||||
"**/pnpm-lock.yaml": true,
|
||||
"**/public": true,
|
||||
"**/temp": true,
|
||||
"**/yarn.lock": true,
|
||||
"**/CHANGELOG*": true,
|
||||
"**/LICENSE*": true,
|
||||
},
|
||||
// ========== Global Level Config, needs to put in User Settings ==========
|
||||
"window.dialogStyle": "custom",
|
||||
"window.nativeTabs": true, // this is great, macOS only
|
||||
"window.title": "${rootName}", // this make tabs more readable
|
||||
"window.titleBarStyle": "custom",
|
||||
"extensions.autoUpdate": "onlyEnabledExtensions",
|
||||
// ========== Extension configs ==========
|
||||
"emmet.showSuggestionsAsSnippets": true,
|
||||
"emmet.triggerExpansionOnTab": false,
|
||||
"errorLens.enabledDiagnosticLevels": [
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"errorLens.excludeBySource": [
|
||||
"cSpell",
|
||||
"Grammarly",
|
||||
"eslint"
|
||||
],
|
||||
// ESLint config: https://github.com/antfu/eslint-config
|
||||
"eslint.codeAction.showDocumentation": {
|
||||
"enable": true
|
||||
},
|
||||
"eslint.quiet": true,
|
||||
// Silent the stylistic rules in you IDE, but still auto fix them
|
||||
"eslint.rules.customizations": [
|
||||
{
|
||||
"rule": "style/*",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "format/*",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-indent",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-spacing",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-spaces",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-order",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-dangle",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*-newline",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*quotes",
|
||||
"severity": "off"
|
||||
},
|
||||
{
|
||||
"rule": "*semi",
|
||||
"severity": "off"
|
||||
}
|
||||
],
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"vue",
|
||||
"html",
|
||||
"markdown",
|
||||
"json",
|
||||
"jsonc",
|
||||
"yaml",
|
||||
"toml"
|
||||
],
|
||||
"github.copilot.enable": {
|
||||
"*": true,
|
||||
"markdown": true,
|
||||
"plaintext": false,
|
||||
},
|
||||
"cSpell.allowCompoundWords": true,
|
||||
"cSpell.language": "en,en-US",
|
||||
"css.lint.hexColorLength": "ignore",
|
||||
"githubIssues.workingIssueFormatScm": "#${issueNumberLabel}",
|
||||
"githubPullRequests.fileListLayout": "tree",
|
||||
"gitlens.codeLens.authors.enabled": false,
|
||||
"gitlens.codeLens.enabled": false,
|
||||
"gitlens.codeLens.recentChange.enabled": false,
|
||||
"gitlens.menus": {
|
||||
"editor": {
|
||||
"blame": false,
|
||||
"clipboard": true,
|
||||
"compare": true,
|
||||
"history": false,
|
||||
"remote": false
|
||||
},
|
||||
"editorGroup": {
|
||||
"blame": true,
|
||||
"compare": false
|
||||
},
|
||||
"editorTab": {
|
||||
"clipboard": true,
|
||||
"compare": true,
|
||||
"history": true,
|
||||
"remote": true
|
||||
},
|
||||
"explorer": {
|
||||
"clipboard": true,
|
||||
"compare": true,
|
||||
"history": true,
|
||||
"remote": true
|
||||
},
|
||||
"scm": {
|
||||
"authors": true
|
||||
},
|
||||
"scmGroup": {
|
||||
"compare": true,
|
||||
"openClose": true,
|
||||
"stash": true
|
||||
},
|
||||
"scmGroupInline": {
|
||||
"stash": true
|
||||
},
|
||||
"scmItem": {
|
||||
"clipboard": true,
|
||||
"compare": true,
|
||||
"history": true,
|
||||
"remote": false,
|
||||
"stash": true
|
||||
}
|
||||
},
|
||||
"i18n-ally.autoDetection": false,
|
||||
"i18n-ally.displayLanguage": "en",
|
||||
"i18n-ally.ignoredLocales": [],
|
||||
"iconify.annotations": true,
|
||||
"iconify.inplace": true,
|
||||
"svg.preview.mode": "svg",
|
||||
// I only use Prettier for manually formatting
|
||||
"prettier.enable": false,
|
||||
"prettier.printWidth": 200,
|
||||
"prettier.semi": false,
|
||||
"prettier.singleQuote": true,
|
||||
// ========== File Nesting ==========
|
||||
// this might not be up to date with the repo, please check yourself
|
||||
// https://github.com/antfu/vscode-file-nesting-config
|
||||
"explorer.fileNesting.enabled": true,
|
||||
"explorer.fileNesting.expand": false,
|
||||
"explorer.fileNesting.patterns": {
|
||||
"*.asax": "$(capture).*.cs, $(capture).*.vb",
|
||||
"*.ascx": "$(capture).*.cs, $(capture).*.vb",
|
||||
"*.ashx": "$(capture).*.cs, $(capture).*.vb",
|
||||
"*.aspx": "$(capture).*.cs, $(capture).*.vb",
|
||||
"*.bloc.dart": "$(capture).event.dart, $(capture).state.dart",
|
||||
"*.c": "$(capture).h",
|
||||
"*.cc": "$(capture).hpp, $(capture).h, $(capture).hxx",
|
||||
"*.cjs": "$(capture).cjs.map, $(capture).*.cjs, $(capture)_*.cjs",
|
||||
"*.component.ts": "$(capture).component.html, $(capture).component.spec.ts, $(capture).component.css, $(capture).component.scss, $(capture).component.sass, $(capture).component.less",
|
||||
"*.cpp": "$(capture).hpp, $(capture).h, $(capture).hxx",
|
||||
"*.cs": "$(capture).*.cs",
|
||||
"*.cshtml": "$(capture).cshtml.cs",
|
||||
"*.csproj": "*.config, *proj.user, appsettings.*, bundleconfig.json",
|
||||
"*.css": "$(capture).css.map, $(capture).*.css",
|
||||
"*.cxx": "$(capture).hpp, $(capture).h, $(capture).hxx",
|
||||
"*.dart": "$(capture).freezed.dart, $(capture).g.dart",
|
||||
"*.ex": "$(capture).html.eex, $(capture).html.heex, $(capture).html.leex",
|
||||
"*.go": "$(capture)_test.go",
|
||||
"*.java": "$(capture).class",
|
||||
"*.js": "$(capture).js.map, $(capture).*.js, $(capture)_*.js",
|
||||
"*.jsx": "$(capture).js, $(capture).*.jsx, $(capture)_*.js, $(capture)_*.jsx",
|
||||
"*.master": "$(capture).*.cs, $(capture).*.vb",
|
||||
"*.mjs": "$(capture).mjs.map, $(capture).*.mjs, $(capture)_*.mjs",
|
||||
"*.module.ts": "$(capture).resolver.ts, $(capture).controller.ts, $(capture).service.ts",
|
||||
"*.pubxml": "$(capture).pubxml.user",
|
||||
"*.resx": "$(capture).*.resx, $(capture).designer.cs, $(capture).designer.vb",
|
||||
"*.tex": "$(capture).acn, $(capture).acr, $(capture).alg, $(capture).aux, $(capture).bbl, $(capture).blg, $(capture).fdb_latexmk, $(capture).fls, $(capture).glg, $(capture).glo, $(capture).gls, $(capture).idx, $(capture).ind, $(capture).ist, $(capture).lof, $(capture).log, $(capture).lot, $(capture).out, $(capture).pdf, $(capture).synctex.gz, $(capture).toc, $(capture).xdv",
|
||||
"*.ts": "$(capture).js, $(capture).d.ts.map, $(capture).*.ts, $(capture)_*.js, $(capture)_*.ts",
|
||||
"*.tsx": "$(capture).ts, $(capture).*.tsx, $(capture)_*.ts, $(capture)_*.tsx",
|
||||
"*.vbproj": "*.config, *proj.user, appsettings.*, bundleconfig.json",
|
||||
"*.vue": "$(capture).*.ts, $(capture).*.js, $(capture).story.vue",
|
||||
"*.xaml": "$(capture).xaml.cs",
|
||||
"+layout.svelte": "+layout.ts,+layout.ts,+layout.js,+layout.server.ts,+layout.server.js,+layout.gql",
|
||||
"+page.svelte": "+page.server.ts,+page.server.js,+page.ts,+page.js,+page.gql",
|
||||
".clang-tidy": ".clang-format, .clangd, compile_commands.json",
|
||||
".env": "*.env, .env.*, .envrc, env.d.ts",
|
||||
".gitignore": ".gitattributes, .gitmodules, .gitmessage, .mailmap, .git-blame*",
|
||||
".project": ".classpath",
|
||||
"//": "Last update at 4/29/2023, 2:04:58 PM",
|
||||
"BUILD.bazel": "*.bzl, *.bazel, *.bazelrc, bazel.rc, .bazelignore, .bazelproject, WORKSPACE",
|
||||
"CMakeLists.txt": "*.cmake, *.cmake.in, .cmake-format.yaml, CMakePresets.json",
|
||||
"I*.cs": "$(capture).cs",
|
||||
"artisan": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, rspack.config.*, server.php, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, webpack.mix.js, windi.config.*",
|
||||
"astro.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, rspack.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*",
|
||||
"cargo.toml": ".clippy.toml, .rustfmt.toml, cargo.lock, clippy.toml, cross.toml, rust-toolchain.toml, rustfmt.toml",
|
||||
"composer.json": ".php*.cache, composer.lock, phpunit.xml*, psalm*.xml",
|
||||
"default.nix": "shell.nix",
|
||||
"deno.json*": "*.env, .env.*, .envrc, api-extractor.json, deno.lock, env.d.ts, import-map.json, import_map.json, jsconfig.*, tsconfig.*, tsdoc.*",
|
||||
"dockerfile": ".dockerignore, docker-compose.*, dockerfile*",
|
||||
"flake.nix": "flake.lock",
|
||||
"gatsby-config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, gatsby-browser.*, gatsby-node.*, gatsby-ssr.*, gatsby-transformer.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, rspack.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*",
|
||||
"gemfile": ".ruby-version, gemfile.lock",
|
||||
"go.mod": ".air*, go.sum",
|
||||
"go.work": "go.work.sum",
|
||||
"mix.exs": ".credo.exs, .dialyzer_ignore.exs, .formatter.exs, .iex.exs, .tool-versions, mix.lock",
|
||||
"next.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, next-env.d.ts, playwright.config.*, postcss.config.*, puppeteer.config.*, rspack.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*",
|
||||
"nuxt.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, rspack.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*",
|
||||
"package.json": ".browserslist*, .circleci*, .commitlint*, .cz-config.js, .czrc, .dlint.json, .dprint.json, .editorconfig, .eslint*, .firebase*, .flowconfig, .github*, .gitlab*, .gitpod*, .huskyrc*, .jslint*, .lintstagedrc*, .markdownlint*, .node-version, .nodemon*, .npm*, .nvmrc, .pm2*, .pnp.*, .pnpm*, .prettier*, .releaserc*, .sentry*, .simple-git-hooks*, .stackblitz*, .styleci*, .stylelint*, .tazerc*, .textlint*, .tool-versions, .travis*, .versionrc*, .vscode*, .watchman*, .xo-config*, .yamllint*, .yarnrc*, Procfile, apollo.config.*, appveyor*, azure-pipelines*, bower.json, build.config.*, commitlint*, crowdin*, dangerfile*, dlint.json, dprint.json, eslint*, firebase.json, grunt*, gulp*, jenkins*, lerna*, lint-staged*, nest-cli.*, netlify*, nodemon*, npm-shrinkwrap.json, nx.*, package-lock.json, package.nls*.json, phpcs.xml, pm2.*, pnpm*, prettier*, pullapprove*, pyrightconfig.json, release-tasks.sh, release.config.*, renovate*, rollup.config.*, rspack*, simple-git-hooks*, stylelint*, tslint*, tsup.config.*, turbo*, typedoc*, unlighthouse*, vercel*, vetur.config.*, webpack*, workspace.json, xo.config.*, yarn*",
|
||||
"pubspec.yaml": ".metadata, .packages, all_lint_rules.yaml, analysis_options.yaml, build.yaml, pubspec.lock, pubspec_overrides.yaml",
|
||||
"pyproject.toml": ".pdm.toml, pdm.lock, pyproject.toml",
|
||||
"quasar.conf.js": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, quasar.extensions.json, rspack.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*",
|
||||
"readme*": "authors, backers*, changelog*, citation*, code_of_conduct*, codeowners, contributing*, contributors, copying, credits, governance.md, history.md, license*, maintainers, readme*, security.md, sponsors*",
|
||||
"remix.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, remix.*, rspack.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*",
|
||||
"rush.json": ".browserslist*, .circleci*, .commitlint*, .cz-config.js, .czrc, .dlint.json, .dprint.json, .editorconfig, .eslint*, .firebase*, .flowconfig, .github*, .gitlab*, .gitpod*, .huskyrc*, .jslint*, .lintstagedrc*, .markdownlint*, .node-version, .nodemon*, .npm*, .nvmrc, .pm2*, .pnp.*, .pnpm*, .prettier*, .releaserc*, .sentry*, .simple-git-hooks*, .stackblitz*, .styleci*, .stylelint*, .tazerc*, .textlint*, .tool-versions, .travis*, .versionrc*, .vscode*, .watchman*, .xo-config*, .yamllint*, .yarnrc*, Procfile, apollo.config.*, appveyor*, azure-pipelines*, bower.json, build.config.*, commitlint*, crowdin*, dangerfile*, dlint.json, dprint.json, eslint*, firebase.json, grunt*, gulp*, jenkins*, lerna*, lint-staged*, nest-cli.*, netlify*, nodemon*, npm-shrinkwrap.json, nx.*, package-lock.json, package.nls*.json, phpcs.xml, pm2.*, pnpm*, prettier*, pullapprove*, pyrightconfig.json, release-tasks.sh, release.config.*, renovate*, rollup.config.*, rspack*, simple-git-hooks*, stylelint*, tslint*, tsup.config.*, turbo*, typedoc*, unlighthouse*, vercel*, vetur.config.*, webpack*, workspace.json, xo.config.*, yarn*",
|
||||
"shims.d.ts": "*.d.ts",
|
||||
"svelte.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, houdini.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, mdsvex.config.js, playwright.config.*, postcss.config.*, puppeteer.config.*, rspack.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vite.config.*, vitest.config.*, webpack.config.*, windi.config.*",
|
||||
"vite.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, rspack.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*",
|
||||
"vue.config.*": "*.env, .babelrc*, .codecov, .cssnanorc*, .env.*, .envrc, .htmlnanorc*, .lighthouserc.*, .mocha*, .postcssrc*, .terserrc*, api-extractor.json, ava.config.*, babel.config.*, contentlayer.config.*, cssnano.config.*, cypress.*, env.d.ts, formkit.config.*, formulate.config.*, histoire.config.*, htmlnanorc.*, jasmine.*, jest.config.*, jsconfig.*, karma*, lighthouserc.*, playwright.config.*, postcss.config.*, puppeteer.config.*, rspack.config.*, svgo.config.*, tailwind.config.*, tsconfig.*, tsdoc.*, uno.config.*, unocss.config.*, vitest.config.*, webpack.config.*, windi.config.*"
|
||||
},
|
||||
"vue3snippets.enable-compile-vue-file-on-did-save-code": false,
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021 Soybean
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* Alova 请求插件配置文件
|
||||
* 作用:自动根据swagger生成对应的api调用函数
|
||||
*/
|
||||
export default {
|
||||
// api生成设置数组,每项代表一个自动生成的规则,包含生成的输入输出目录、规范文件地址等等
|
||||
generator: [
|
||||
// 服务器1
|
||||
{
|
||||
// input参数1:openapi的json文件url地址
|
||||
input: 'http://127.0.0.1:4523/export/openapi/8?version=3.0',
|
||||
|
||||
// input参数2:以当前项目为相对目录的本地地址
|
||||
// input: 'openapi/api.json'
|
||||
|
||||
// input参数3:没有直接指向openapi文件时,是一个文档地址,必须配合platform参数指定文档类型
|
||||
// input: 'http://192.168.5.123:8080'
|
||||
|
||||
// (可选)platform为支持openapi的平台,目前只支持swagger,默认为空
|
||||
// 当指定了此参数后,input字段只需要指定文档的地址而不需要指定到openapi文件
|
||||
platform: 'swagger',
|
||||
|
||||
// 接口文件和类型文件的输出路径,多个generator不能重复的地址,否则生成的代码会相互覆盖
|
||||
output: 'src/api',
|
||||
|
||||
// (可选)指定生成的响应数据的mediaType,以此数据类型来生成200状态码的响应ts格式,默认application/json
|
||||
responseMediaType: 'application/json',
|
||||
|
||||
// (可选)指定生成的请求体数据的bodyMediaType,以此数据类型来生成请求体的ts格式,默认application/json
|
||||
bodyMediaType: 'application/json',
|
||||
|
||||
// (可选)指定生成的api版本,默认为auto,会通过当前项目安装的alova版本判断当前项目的版本,如果生成不正确你也可以自定义指定版本
|
||||
version: 'auto',
|
||||
|
||||
/**
|
||||
* (可选)生成代码的类型,可选值为auto/ts/typescript/module/commonjs,默认为auto,会通过一定规则判断当前项目的类型,如果生成不正确你也可以自定义指定类型:
|
||||
* ts/typescript:意思相同,表示生成ts类型文件
|
||||
* module:生成esModule规范文件
|
||||
* commonjs:表示生成commonjs规范文件
|
||||
*/
|
||||
type: 'typescript',
|
||||
|
||||
/**
|
||||
* 全局导出的api名称,可通过此名称全局范围访问自动生成的api,默认为`Apis`,配置了多个generator时为必填,且不可以重复
|
||||
*/
|
||||
global: 'Apis',
|
||||
|
||||
/**
|
||||
* (可选)过滤或转换生成的api接口函数,返回一个新的apiDescriptor来生成api调用函数,未指定此函数时则不转换apiDescripor对象
|
||||
*/
|
||||
handleApi: apiDescriptor => {
|
||||
// 返回falsy值表示过滤此api
|
||||
// if (!apiDescriptor.path.startWith('/user')) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// apiDescriptor.parameter = apiDescriptor.parameter.filter(
|
||||
// param => param.in === 'header' && param.name === 'token'
|
||||
// );
|
||||
// delete apiDescriptor.requestBody.id;
|
||||
// apiDescriptor.url = '/app/rl' + apiDescriptor.url;
|
||||
apiDescriptor.url = '/app/rl' + apiDescriptor.url;
|
||||
return apiDescriptor;
|
||||
}
|
||||
},
|
||||
],
|
||||
|
||||
// (可选)是否自动更新接口,默认开启,每5分钟检查一次,false时关闭
|
||||
autoUpdate: false
|
||||
|
||||
/* 也可以配置更详细的参数
|
||||
autoUpdate: {
|
||||
// 编辑器开启时更新,默认false
|
||||
launchEditor: true,
|
||||
// 自动更新间隔,单位毫秒
|
||||
interval: 5 * 60 * 1000
|
||||
}
|
||||
*/
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
export * from './proxy';
|
|
@ -0,0 +1,36 @@
|
|||
import type { ProxyOptions } from 'vite';
|
||||
import { createServiceConfig } from '../../src/utils/service';
|
||||
|
||||
/**
|
||||
* Set http proxy
|
||||
*
|
||||
* @param env - The current env
|
||||
* @param isDev - Is development environment
|
||||
*/
|
||||
export function createViteProxy(env: Env.ImportMeta, isDev: boolean) {
|
||||
const isEnableHttpProxy = isDev && env.VITE_HTTP_PROXY === 'Y';
|
||||
|
||||
if (!isEnableHttpProxy) return undefined;
|
||||
|
||||
const { baseURL, proxyPattern, other } = createServiceConfig(env);
|
||||
|
||||
const proxy: Record<string, ProxyOptions> = createProxyItem({ baseURL, proxyPattern });
|
||||
|
||||
other.forEach(item => {
|
||||
Object.assign(proxy, createProxyItem(item));
|
||||
});
|
||||
|
||||
return proxy;
|
||||
}
|
||||
|
||||
function createProxyItem(item: App.Service.ServiceConfigItem) {
|
||||
const proxy: Record<string, ProxyOptions> = {};
|
||||
|
||||
proxy[item.proxyPattern] = {
|
||||
target: item.baseURL,
|
||||
changeOrigin: true,
|
||||
rewrite: path => path.replace(new RegExp(`^${item.proxyPattern}`), '')
|
||||
};
|
||||
|
||||
return proxy;
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import type { PluginOption } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import vueJsx from '@vitejs/plugin-vue-jsx';
|
||||
import VueDevtools from 'vite-plugin-vue-devtools';
|
||||
import progress from 'vite-plugin-progress';
|
||||
import { setupElegantRouter } from './router';
|
||||
import { setupUnocss } from './unocss';
|
||||
import { setupUnplugin } from './unplugin';
|
||||
|
||||
import { lazyImport, VxeResolver } from 'vite-plugin-lazy-import'
|
||||
|
||||
|
||||
export function setupVitePlugins(viteEnv: Env.ImportMeta) {
|
||||
const plugins: PluginOption = [
|
||||
vue({
|
||||
script: {
|
||||
defineModel: true
|
||||
}
|
||||
}),
|
||||
vueJsx(),
|
||||
VueDevtools(),
|
||||
setupElegantRouter(),
|
||||
setupUnocss(viteEnv),
|
||||
...setupUnplugin(viteEnv),
|
||||
progress(),
|
||||
// lazyImport({
|
||||
// resolvers: [
|
||||
// VxeResolver({
|
||||
// libraryName: 'vxe-table'
|
||||
// }),
|
||||
// VxeResolver({
|
||||
// libraryName: 'vxe-pc-ui'
|
||||
// })
|
||||
// ]
|
||||
// })
|
||||
];
|
||||
|
||||
return plugins;
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
import type { RouteMeta } from 'vue-router';
|
||||
import ElegantVueRouter from '@elegant-router/vue/vite';
|
||||
import type { RouteKey } from '@elegant-router/types';
|
||||
|
||||
export function setupElegantRouter() {
|
||||
return ElegantVueRouter({
|
||||
layouts: {
|
||||
base: 'src/layouts/base-layout/index.vue',
|
||||
blank: 'src/layouts/blank-layout/index.vue'
|
||||
},
|
||||
customRoutes: {
|
||||
names: [
|
||||
'exception_403',
|
||||
'exception_404',
|
||||
'exception_500',
|
||||
'document_project',
|
||||
'document_project-link',
|
||||
'document_vue',
|
||||
'document_vite',
|
||||
'document_unocss',
|
||||
'document_naive',
|
||||
'document_antd'
|
||||
]
|
||||
},
|
||||
routePathTransformer(routeName, routePath) {
|
||||
const key = routeName as RouteKey;
|
||||
|
||||
if (key === 'login') {
|
||||
const modules: UnionKey.LoginModule[] = ['pwd-login', 'code-login', 'register', 'reset-pwd', 'bind-wechat'];
|
||||
|
||||
const moduleReg = modules.join('|');
|
||||
|
||||
return `/login/:module(${moduleReg})?`;
|
||||
}
|
||||
|
||||
if (key === "meeting_edit") {
|
||||
return "/meeting/edit/:id?";
|
||||
}
|
||||
|
||||
if (key === 'contract_approval_edit') {
|
||||
return "/contract/approval/edit/:id?";
|
||||
}
|
||||
|
||||
return routePath;
|
||||
},
|
||||
onRouteMetaGen(routeName) {
|
||||
const key = routeName as RouteKey;
|
||||
|
||||
const constantRoutes: RouteKey[] = ['login', '403', '404', '500'];
|
||||
|
||||
const meta: Partial<RouteMeta> = {
|
||||
title: key,
|
||||
// i18nKey: `route.${key}` as App.I18n.I18nKey
|
||||
};
|
||||
|
||||
if (constantRoutes.includes(key)) {
|
||||
meta.constant = true;
|
||||
}
|
||||
|
||||
return meta;
|
||||
}
|
||||
});
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import process from 'node:process';
|
||||
import path from 'node:path';
|
||||
import unocss from '@unocss/vite';
|
||||
import presetIcons from '@unocss/preset-icons';
|
||||
import { FileSystemIconLoader } from '@iconify/utils/lib/loader/node-loaders';
|
||||
|
||||
export function setupUnocss(viteEnv: Env.ImportMeta) {
|
||||
const { VITE_ICON_PREFIX, VITE_ICON_LOCAL_PREFIX } = viteEnv;
|
||||
|
||||
const localIconPath = path.join(process.cwd(), 'src/assets/svg-icon');
|
||||
|
||||
/** The name of the local icon collection */
|
||||
const collectionName = VITE_ICON_LOCAL_PREFIX.replace(`${VITE_ICON_PREFIX}-`, '');
|
||||
|
||||
return unocss({
|
||||
presets: [
|
||||
presetIcons({
|
||||
// prefix: `${VITE_ICON_PREFIX}-`,
|
||||
scale: 1.2,
|
||||
extraProperties: {
|
||||
display: 'inline-block'
|
||||
},
|
||||
collections: {
|
||||
[collectionName]: FileSystemIconLoader(localIconPath, svg =>
|
||||
svg.replace(/^<svg\s/, '<svg width="1em" height="1em" ')
|
||||
)
|
||||
},
|
||||
warn: true
|
||||
})
|
||||
]
|
||||
});
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
import process from 'node:process';
|
||||
import path from 'node:path';
|
||||
import type { PluginOption } from 'vite';
|
||||
import Icons from 'unplugin-icons/vite';
|
||||
import IconsResolver from 'unplugin-icons/resolver';
|
||||
import Components from 'unplugin-vue-components/vite';
|
||||
import { AntDesignVueResolver, NaiveUiResolver } from 'unplugin-vue-components/resolvers';
|
||||
import { FileSystemIconLoader } from 'unplugin-icons/loaders';
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons';
|
||||
|
||||
export function setupUnplugin(viteEnv: Env.ImportMeta) {
|
||||
const { VITE_ICON_PREFIX, VITE_ICON_LOCAL_PREFIX } = viteEnv;
|
||||
|
||||
const localIconPath = path.join(process.cwd(), 'src/assets/svg-icon');
|
||||
|
||||
/** The name of the local icon collection */
|
||||
const collectionName = VITE_ICON_LOCAL_PREFIX.replace(`${VITE_ICON_PREFIX}-`, '');
|
||||
|
||||
const plugins: PluginOption[] = [
|
||||
Icons({
|
||||
compiler: 'vue3',
|
||||
customCollections: {
|
||||
[collectionName]: FileSystemIconLoader(localIconPath, svg =>
|
||||
svg.replace(/^<svg\s/, '<svg width="1em" height="1em" ')
|
||||
)
|
||||
},
|
||||
scale: 1,
|
||||
defaultClass: 'inline-block'
|
||||
}),
|
||||
Components({
|
||||
dirs: ['src/components'],
|
||||
extensions: ['vue'],
|
||||
dts: 'src/types/components.d.ts',
|
||||
types: [{ from: 'vue-router', names: ['RouterLink', 'RouterView'] }],
|
||||
resolvers: [
|
||||
AntDesignVueResolver({
|
||||
importStyle: false
|
||||
}),
|
||||
NaiveUiResolver(),
|
||||
IconsResolver({ customCollections: [collectionName], componentPrefix: VITE_ICON_PREFIX })
|
||||
]
|
||||
}),
|
||||
createSvgIconsPlugin({
|
||||
iconDirs: [localIconPath],
|
||||
symbolId: `${VITE_ICON_LOCAL_PREFIX}-[dir]-[name]`,
|
||||
inject: 'body-last',
|
||||
customDomId: '__SVG_ICON_LOCAL__'
|
||||
})
|
||||
];
|
||||
|
||||
return plugins;
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
// eslint.config.js
|
||||
import antfu from '@antfu/eslint-config'
|
||||
|
||||
export default antfu(
|
||||
{
|
||||
// Configures for antfu's config
|
||||
// Enable stylistic formatting rules
|
||||
stylistic: true,
|
||||
// TypeScript and Vue are auto-detected, you can also explicitly enable them:
|
||||
typescript: true,
|
||||
vue: true,
|
||||
|
||||
// Disable jsonc and yaml support
|
||||
jsonc: false,
|
||||
yaml: false,
|
||||
formatters: {
|
||||
/**
|
||||
* Format CSS, LESS, SCSS files, also the `<style>` blocks in Vue
|
||||
* By default uses Prettier
|
||||
*/
|
||||
css: true,
|
||||
/**
|
||||
* Format HTML files
|
||||
* By default uses Prettier
|
||||
*/
|
||||
html: true,
|
||||
/**
|
||||
* Format Markdown files
|
||||
* Supports Prettier and dprint
|
||||
* By default uses Prettier
|
||||
*/
|
||||
markdown: 'prettier',
|
||||
},
|
||||
},
|
||||
|
||||
// From the second arguments they are ESLint Flat Configs
|
||||
// you can have multiple configs
|
||||
{
|
||||
files: ['**/*.ts'],
|
||||
rules: {},
|
||||
},
|
||||
{
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
'style/semi': ['error', 'never'],
|
||||
},
|
||||
},
|
||||
)
|
|
@ -0,0 +1,29 @@
|
|||
<!doctype html>
|
||||
<html lang="zh-cmn-Hans">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>%VITE_APP_TITLE%</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
<script type="text/javascript">
|
||||
(function () {
|
||||
var ua = navigator.userAgent.toLocaleLowerCase();
|
||||
var browserType = "", browserVersion = "";
|
||||
if (ua.match(/msie/) != null || ua.match(/trident/) != null) {
|
||||
browserType = "IE";
|
||||
browserVersion = ua.match(/msie ([\d.]+)/) != null ? ua.match(/msie ([\d.]+)/)[1] : ua.match(/rv:([\d.]+)/)[1];
|
||||
if (1 * browserVersion < 12) {
|
||||
alert("请在ie11版本浏览器上使用系统");
|
||||
}
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,14 @@
|
|||
export function ok(data?: any) {
|
||||
return {
|
||||
code: 0,
|
||||
data: data || {},
|
||||
msg: ''
|
||||
}
|
||||
}
|
||||
|
||||
export function err(data?: any) {
|
||||
return {
|
||||
code: 0,
|
||||
data: data || {},
|
||||
msg: ''
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
import { defineMock } from '@alova/mock';
|
||||
import { ok, err } from '../common'
|
||||
export default defineMock(
|
||||
{
|
||||
// 捕获get请求
|
||||
'/todo': ok({
|
||||
nickname: ''
|
||||
}),
|
||||
},
|
||||
true
|
||||
);
|
|
@ -0,0 +1,108 @@
|
|||
{
|
||||
"name": "pl-webbase-src",
|
||||
"version": "naive-ui-admin",
|
||||
"author": {
|
||||
"name": "author",
|
||||
"email": "aa@qq.com",
|
||||
"url": "https://xx.git"
|
||||
},
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=18.12.0",
|
||||
"pnpm": ">=8.7.0"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "vite build --mode prod",
|
||||
"build:test": "vite build --mode test",
|
||||
"cleanup": "sa cleanup",
|
||||
"commit": "sa git-commit",
|
||||
"dev": "vite --mode test",
|
||||
"dev:prod": "vite --mode prod",
|
||||
"gen-route": "sa gen-route",
|
||||
"preview": "vite preview",
|
||||
"release": "sa release",
|
||||
"typecheck": "vue-tsc --noEmit --skipLibCheck",
|
||||
"update-pkg": "sa update-pkg",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@alova/mock": "2.0.0-beta.11",
|
||||
"@better-scroll/core": "2.5.1",
|
||||
"@iconify/vue": "4.1.2",
|
||||
"@sa/color": "workspace:*",
|
||||
"@sa/hooks": "workspace:*",
|
||||
"@sa/materials": "workspace:*",
|
||||
"@sa/utils": "workspace:*",
|
||||
"@vicons/antd": "^0.12.0",
|
||||
"@vueuse/core": "10.9.0",
|
||||
"@vxe-ui/plugin-render-naive": "workspace:*",
|
||||
"alova": "3.0.0-beta.14",
|
||||
"big.js": "^6.2.1",
|
||||
"clipboard": "2.0.11",
|
||||
"dayjs": "1.11.11",
|
||||
"echarts": "5.5.0",
|
||||
"exceljs": "^4.4.0",
|
||||
"lodash-es": "4.17.21",
|
||||
"mitt": "^3.0.1",
|
||||
"mockjs": "^1.1.0",
|
||||
"naive-ui": "2.38.2",
|
||||
"nprogress": "0.2.0",
|
||||
"pinia": "2.1.7",
|
||||
"pinia-plugin-persistedstate": "^3.2.1",
|
||||
"sortablejs": "^1.15.2",
|
||||
"tyme4ts": "^1.0.7",
|
||||
"vfonts": "^0.0.3",
|
||||
"vue": "3.4.27",
|
||||
"vue-draggable-plus": "0.4.1",
|
||||
"vue-i18n": "9.13.1",
|
||||
"vue-router": "4.3.2",
|
||||
"vue-types": "^5.1.2",
|
||||
"vxe-pc-ui": "^4.0.44",
|
||||
"vxe-table": "^4.7.40",
|
||||
"vxe-table-plugin-export-xlsx": "^4.0.5",
|
||||
"xe-utils": "^3.5.26"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^2.21.1",
|
||||
"@elegant-router/vue": "0.3.7",
|
||||
"@iconify-json/icon-park-outline": "^1.1.15",
|
||||
"@iconify/json": "2.2.211",
|
||||
"@sa/scripts": "workspace:*",
|
||||
"@sa/uno-preset": "workspace:*",
|
||||
"@types/big.js": "^6.2.2",
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"@types/node": "20.12.12",
|
||||
"@types/nprogress": "0.2.3",
|
||||
"@unocss/eslint-config": "0.60.2",
|
||||
"@unocss/preset-icons": "0.60.2",
|
||||
"@unocss/preset-uno": "0.60.2",
|
||||
"@unocss/transformer-directives": "0.60.2",
|
||||
"@unocss/transformer-variant-group": "0.60.2",
|
||||
"@unocss/vite": "0.60.2",
|
||||
"@vitejs/plugin-vue": "5.0.4",
|
||||
"@vitejs/plugin-vue-jsx": "3.1.0",
|
||||
"eslint": "9.3.0",
|
||||
"eslint-plugin-format": "^0.1.2",
|
||||
"eslint-plugin-vue": "9.26.0",
|
||||
"lint-staged": "15.2.2",
|
||||
"sass": "1.77.2",
|
||||
"seemly": "^0.3.8",
|
||||
"tsx": "4.10.5",
|
||||
"typescript": "5.4.5",
|
||||
"unplugin-icons": "0.19.0",
|
||||
"unplugin-vue-components": "0.27.0",
|
||||
"vite": "5.2.11",
|
||||
"vite-plugin-lazy-import": "^1.0.7",
|
||||
"vite-plugin-mock": "^2.9.8",
|
||||
"vite-plugin-progress": "0.0.7",
|
||||
"vite-plugin-svg-icons": "2.0.1",
|
||||
"vite-plugin-vue-devtools": "7.2.0",
|
||||
"vue-eslint-parser": "9.4.2",
|
||||
"vue-tsc": "2.0.19"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*": "eslint --fix"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"name": "@sa/axios",
|
||||
"version": "1.1.1",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@sa/utils": "workspace:*",
|
||||
"axios": "1.6.8",
|
||||
"axios-retry": "4.2.0",
|
||||
"qs": "6.12.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/qs": "6.9.15"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
/** request id key */
|
||||
export const REQUEST_ID_KEY = 'X-Request-Id';
|
||||
|
||||
/** the backend error code key */
|
||||
export const BACKEND_ERROR_CODE = 'BACKEND_ERROR';
|
|
@ -0,0 +1,181 @@
|
|||
import axios, { AxiosError } from 'axios';
|
||||
import type { AxiosResponse, CancelTokenSource, CreateAxiosDefaults, InternalAxiosRequestConfig } from 'axios';
|
||||
import axiosRetry from 'axios-retry';
|
||||
import { nanoid } from '@sa/utils';
|
||||
import { createAxiosConfig, createDefaultOptions, createRetryOptions } from './options';
|
||||
import { BACKEND_ERROR_CODE, REQUEST_ID_KEY } from './constant';
|
||||
import type {
|
||||
CustomAxiosRequestConfig,
|
||||
FlatRequestInstance,
|
||||
MappedType,
|
||||
RequestInstance,
|
||||
RequestOption,
|
||||
ResponseType
|
||||
} from './type';
|
||||
|
||||
function createCommonRequest<ResponseData = any>(
|
||||
axiosConfig?: CreateAxiosDefaults,
|
||||
options?: Partial<RequestOption<ResponseData>>
|
||||
) {
|
||||
const opts = createDefaultOptions<ResponseData>(options);
|
||||
|
||||
const axiosConf = createAxiosConfig(axiosConfig);
|
||||
const instance = axios.create(axiosConf);
|
||||
|
||||
const cancelTokenSourceMap = new Map<string, CancelTokenSource>();
|
||||
|
||||
// config axios retry
|
||||
const retryOptions = createRetryOptions(axiosConf);
|
||||
axiosRetry(instance, retryOptions);
|
||||
|
||||
instance.interceptors.request.use(conf => {
|
||||
const config: InternalAxiosRequestConfig = { ...conf };
|
||||
|
||||
// set request id
|
||||
const requestId = nanoid();
|
||||
config.headers.set(REQUEST_ID_KEY, requestId);
|
||||
|
||||
// config cancel token
|
||||
const cancelTokenSource = axios.CancelToken.source();
|
||||
config.cancelToken = cancelTokenSource.token;
|
||||
cancelTokenSourceMap.set(requestId, cancelTokenSource);
|
||||
|
||||
// handle config by hook
|
||||
const handledConfig = opts.onRequest?.(config) || config;
|
||||
|
||||
return handledConfig;
|
||||
});
|
||||
|
||||
instance.interceptors.response.use(
|
||||
async response => {
|
||||
const responseType: ResponseType = (response.config?.responseType as ResponseType) || 'json';
|
||||
|
||||
if (responseType !== 'json' || opts.isBackendSuccess(response)) {
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
|
||||
const fail = await opts.onBackendFail(response, instance);
|
||||
if (fail) {
|
||||
return fail;
|
||||
}
|
||||
|
||||
const backendError = new AxiosError<ResponseData>(
|
||||
'the backend request error',
|
||||
BACKEND_ERROR_CODE,
|
||||
response.config,
|
||||
response.request,
|
||||
response
|
||||
);
|
||||
|
||||
await opts.onError(backendError);
|
||||
|
||||
return Promise.reject(backendError);
|
||||
},
|
||||
async (error: AxiosError<ResponseData>) => {
|
||||
await opts.onError(error);
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
function cancelRequest(requestId: string) {
|
||||
const cancelTokenSource = cancelTokenSourceMap.get(requestId);
|
||||
if (cancelTokenSource) {
|
||||
cancelTokenSource.cancel();
|
||||
cancelTokenSourceMap.delete(requestId);
|
||||
}
|
||||
}
|
||||
|
||||
function cancelAllRequest() {
|
||||
cancelTokenSourceMap.forEach(cancelTokenSource => {
|
||||
cancelTokenSource.cancel();
|
||||
});
|
||||
cancelTokenSourceMap.clear();
|
||||
}
|
||||
|
||||
return {
|
||||
instance,
|
||||
opts,
|
||||
cancelRequest,
|
||||
cancelAllRequest
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* create a request instance
|
||||
*
|
||||
* @param axiosConfig axios config
|
||||
* @param options request options
|
||||
*/
|
||||
export function createRequest<ResponseData = any, State = Record<string, unknown>>(
|
||||
axiosConfig?: CreateAxiosDefaults,
|
||||
options?: Partial<RequestOption<ResponseData>>
|
||||
) {
|
||||
const { instance, opts, cancelRequest, cancelAllRequest } = createCommonRequest<ResponseData>(axiosConfig, options);
|
||||
|
||||
const request: RequestInstance<State> = async function request<T = any, R extends ResponseType = 'json'>(
|
||||
config: CustomAxiosRequestConfig
|
||||
) {
|
||||
const response: AxiosResponse<ResponseData> = await instance(config);
|
||||
|
||||
const responseType = response.config?.responseType || 'json';
|
||||
|
||||
if (responseType === 'json') {
|
||||
return opts.transformBackendResponse(response);
|
||||
}
|
||||
|
||||
return response.data as MappedType<R, T>;
|
||||
} as RequestInstance<State>;
|
||||
|
||||
request.cancelRequest = cancelRequest;
|
||||
request.cancelAllRequest = cancelAllRequest;
|
||||
request.state = {} as State;
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* create a flat request instance
|
||||
*
|
||||
* The response data is a flat object: { data: any, error: AxiosError }
|
||||
*
|
||||
* @param axiosConfig axios config
|
||||
* @param options request options
|
||||
*/
|
||||
export function createFlatRequest<ResponseData = any, State = Record<string, unknown>>(
|
||||
axiosConfig?: CreateAxiosDefaults,
|
||||
options?: Partial<RequestOption<ResponseData>>
|
||||
) {
|
||||
const { instance, opts, cancelRequest, cancelAllRequest } = createCommonRequest<ResponseData>(axiosConfig, options);
|
||||
|
||||
const flatRequest: FlatRequestInstance<State, ResponseData> = async function flatRequest<
|
||||
T = any,
|
||||
R extends ResponseType = 'json'
|
||||
>(config: CustomAxiosRequestConfig) {
|
||||
try {
|
||||
const response: AxiosResponse<ResponseData> = await instance(config);
|
||||
|
||||
const responseType = response.config?.responseType || 'json';
|
||||
|
||||
if (responseType === 'json') {
|
||||
const data = opts.transformBackendResponse(response);
|
||||
|
||||
return { data, error: null };
|
||||
}
|
||||
|
||||
return { data: response.data as MappedType<R, T>, error: null };
|
||||
} catch (error) {
|
||||
return { data: null, error };
|
||||
}
|
||||
} as FlatRequestInstance<State, ResponseData>;
|
||||
|
||||
flatRequest.cancelRequest = cancelRequest;
|
||||
flatRequest.cancelAllRequest = cancelAllRequest;
|
||||
flatRequest.state = {} as State;
|
||||
|
||||
return flatRequest;
|
||||
}
|
||||
|
||||
export { BACKEND_ERROR_CODE, REQUEST_ID_KEY };
|
||||
export type * from './type';
|
||||
export type { CreateAxiosDefaults, AxiosError };
|
|
@ -0,0 +1,48 @@
|
|||
import type { CreateAxiosDefaults } from 'axios';
|
||||
import type { IAxiosRetryConfig } from 'axios-retry';
|
||||
import { stringify } from 'qs';
|
||||
import { isHttpSuccess } from './shared';
|
||||
import type { RequestOption } from './type';
|
||||
|
||||
export function createDefaultOptions<ResponseData = any>(options?: Partial<RequestOption<ResponseData>>) {
|
||||
const opts: RequestOption<ResponseData> = {
|
||||
onRequest: async config => config,
|
||||
isBackendSuccess: _response => true,
|
||||
onBackendFail: async () => {},
|
||||
transformBackendResponse: async response => response.data,
|
||||
onError: async () => {}
|
||||
};
|
||||
|
||||
Object.assign(opts, options);
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
export function createRetryOptions(config?: Partial<CreateAxiosDefaults>) {
|
||||
const retryConfig: IAxiosRetryConfig = {
|
||||
retries: 3
|
||||
};
|
||||
|
||||
Object.assign(retryConfig, config);
|
||||
|
||||
return retryConfig;
|
||||
}
|
||||
|
||||
export function createAxiosConfig(config?: Partial<CreateAxiosDefaults>) {
|
||||
const TEN_SECONDS = 10 * 1000;
|
||||
|
||||
const axiosConfig: CreateAxiosDefaults = {
|
||||
timeout: TEN_SECONDS,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
validateStatus: isHttpSuccess,
|
||||
paramsSerializer: params => {
|
||||
return stringify(params);
|
||||
}
|
||||
};
|
||||
|
||||
Object.assign(axiosConfig, config);
|
||||
|
||||
return axiosConfig;
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import type { AxiosHeaderValue, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
||||
|
||||
export function getContentType(config: InternalAxiosRequestConfig) {
|
||||
const contentType: AxiosHeaderValue = config.headers?.['Content-Type'] || 'application/json';
|
||||
|
||||
return contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if http status is success
|
||||
*
|
||||
* @param status
|
||||
*/
|
||||
export function isHttpSuccess(status: number) {
|
||||
const isSuccessCode = status >= 200 && status < 300;
|
||||
return isSuccessCode || status === 304;
|
||||
}
|
||||
|
||||
/**
|
||||
* is response json
|
||||
*
|
||||
* @param response axios response
|
||||
*/
|
||||
export function isResponseJson(response: AxiosResponse) {
|
||||
const { responseType } = response.config;
|
||||
|
||||
return responseType === 'json' || responseType === undefined;
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
||||
|
||||
export type ContentType =
|
||||
| 'text/html'
|
||||
| 'text/plain'
|
||||
| 'multipart/form-data'
|
||||
| 'application/json'
|
||||
| 'application/x-www-form-urlencoded'
|
||||
| 'application/octet-stream';
|
||||
|
||||
export interface RequestOption<ResponseData = any> {
|
||||
/**
|
||||
* The hook before request
|
||||
*
|
||||
* For example: You can add header token in this hook
|
||||
*
|
||||
* @param config Axios config
|
||||
*/
|
||||
onRequest: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>;
|
||||
/**
|
||||
* The hook to check backend response is success or not
|
||||
*
|
||||
* @param response Axios response
|
||||
*/
|
||||
isBackendSuccess: (response: AxiosResponse<ResponseData>) => boolean;
|
||||
/**
|
||||
* The hook after backend request fail
|
||||
*
|
||||
* For example: You can handle the expired token in this hook
|
||||
*
|
||||
* @param response Axios response
|
||||
* @param instance Axios instance
|
||||
*/
|
||||
onBackendFail: (
|
||||
response: AxiosResponse<ResponseData>,
|
||||
instance: AxiosInstance
|
||||
) => Promise<AxiosResponse | null> | Promise<void>;
|
||||
/**
|
||||
* transform backend response when the responseType is json
|
||||
*
|
||||
* @param response Axios response
|
||||
*/
|
||||
transformBackendResponse(response: AxiosResponse<ResponseData>): any | Promise<any>;
|
||||
/**
|
||||
* The hook to handle error
|
||||
*
|
||||
* For example: You can show error message in this hook
|
||||
*
|
||||
* @param error
|
||||
*/
|
||||
onError: (error: AxiosError<ResponseData>) => void | Promise<void>;
|
||||
}
|
||||
|
||||
interface ResponseMap {
|
||||
blob: Blob;
|
||||
text: string;
|
||||
arrayBuffer: ArrayBuffer;
|
||||
stream: ReadableStream<Uint8Array>;
|
||||
document: Document;
|
||||
}
|
||||
export type ResponseType = keyof ResponseMap | 'json';
|
||||
|
||||
export type MappedType<R extends ResponseType, JsonType = any> = R extends keyof ResponseMap
|
||||
? ResponseMap[R]
|
||||
: JsonType;
|
||||
|
||||
export type CustomAxiosRequestConfig<R extends ResponseType = 'json'> = Omit<AxiosRequestConfig, 'responseType'> & {
|
||||
responseType?: R;
|
||||
};
|
||||
|
||||
export interface RequestInstanceCommon<T> {
|
||||
cancelRequest: (requestId: string) => void;
|
||||
cancelAllRequest: () => void;
|
||||
/** you can set custom state in the request instance */
|
||||
state: T;
|
||||
}
|
||||
|
||||
/** The request instance */
|
||||
export interface RequestInstance<S = Record<string, unknown>> extends RequestInstanceCommon<S> {
|
||||
<T = any, R extends ResponseType = 'json'>(config: CustomAxiosRequestConfig<R>): Promise<MappedType<R, T>>;
|
||||
}
|
||||
|
||||
export type FlatResponseSuccessData<T = any> = {
|
||||
data: T;
|
||||
error: null;
|
||||
};
|
||||
|
||||
export type FlatResponseFailData<ResponseData = any> = {
|
||||
data: null;
|
||||
error: AxiosError<ResponseData>;
|
||||
};
|
||||
|
||||
export type FlatResponseData<T = any, ResponseData = any> =
|
||||
| FlatResponseSuccessData<T>
|
||||
| FlatResponseFailData<ResponseData>;
|
||||
|
||||
export interface FlatRequestInstance<S = Record<string, unknown>, ResponseData = any> extends RequestInstanceCommon<S> {
|
||||
<T = any, R extends ResponseType = 'json'>(
|
||||
config: CustomAxiosRequestConfig<R>
|
||||
): Promise<FlatResponseData<MappedType<R, T>, ResponseData>>;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"jsx": "preserve",
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"noUnusedLocals": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "@sa/color",
|
||||
"version": "1.1.1",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@sa/utils": "workspace:*",
|
||||
"colord": "2.9.3"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * from './name';
|
||||
export * from './palette';
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,356 @@
|
|||
import type { ColorPaletteFamily } from '../types';
|
||||
|
||||
export const colorPalettes: ColorPaletteFamily[] = [
|
||||
{
|
||||
name: 'Slate',
|
||||
palettes: [
|
||||
{ hex: '#f8fafc', number: 50 },
|
||||
{ hex: '#f1f5f9', number: 100 },
|
||||
{ hex: '#e2e8f0', number: 200 },
|
||||
{ hex: '#cbd5e1', number: 300 },
|
||||
{ hex: '#94a3b8', number: 400 },
|
||||
{ hex: '#64748b', number: 500 },
|
||||
{ hex: '#475569', number: 600 },
|
||||
{ hex: '#334155', number: 700 },
|
||||
{ hex: '#1e293b', number: 800 },
|
||||
{ hex: '#0f172a', number: 900 },
|
||||
{ hex: '#020617', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Gray',
|
||||
palettes: [
|
||||
{ hex: '#f9fafb', number: 50 },
|
||||
{ hex: '#f3f4f6', number: 100 },
|
||||
{ hex: '#e5e7eb', number: 200 },
|
||||
{ hex: '#d1d5db', number: 300 },
|
||||
{ hex: '#9ca3af', number: 400 },
|
||||
{ hex: '#6b7280', number: 500 },
|
||||
{ hex: '#4b5563', number: 600 },
|
||||
{ hex: '#374151', number: 700 },
|
||||
{ hex: '#1f2937', number: 800 },
|
||||
{ hex: '#111827', number: 900 },
|
||||
{ hex: '#030712', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Zinc',
|
||||
palettes: [
|
||||
{ hex: '#fafafa', number: 50 },
|
||||
{ hex: '#f4f4f5', number: 100 },
|
||||
{ hex: '#e4e4e7', number: 200 },
|
||||
{ hex: '#d4d4d8', number: 300 },
|
||||
{ hex: '#a1a1aa', number: 400 },
|
||||
{ hex: '#71717a', number: 500 },
|
||||
{ hex: '#52525b', number: 600 },
|
||||
{ hex: '#3f3f46', number: 700 },
|
||||
{ hex: '#27272a', number: 800 },
|
||||
{ hex: '#18181b', number: 900 },
|
||||
{ hex: '#09090b', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Neutral',
|
||||
palettes: [
|
||||
{ hex: '#fafafa', number: 50 },
|
||||
{ hex: '#f5f5f5', number: 100 },
|
||||
{ hex: '#e5e5e5', number: 200 },
|
||||
{ hex: '#d4d4d4', number: 300 },
|
||||
{ hex: '#a3a3a3', number: 400 },
|
||||
{ hex: '#737373', number: 500 },
|
||||
{ hex: '#525252', number: 600 },
|
||||
{ hex: '#404040', number: 700 },
|
||||
{ hex: '#262626', number: 800 },
|
||||
{ hex: '#171717', number: 900 },
|
||||
{ hex: '#0a0a0a', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Stone',
|
||||
palettes: [
|
||||
{ hex: '#fafaf9', number: 50 },
|
||||
{ hex: '#f5f5f4', number: 100 },
|
||||
{ hex: '#e7e5e4', number: 200 },
|
||||
{ hex: '#d6d3d1', number: 300 },
|
||||
{ hex: '#a8a29e', number: 400 },
|
||||
{ hex: '#78716c', number: 500 },
|
||||
{ hex: '#57534e', number: 600 },
|
||||
{ hex: '#44403c', number: 700 },
|
||||
{ hex: '#292524', number: 800 },
|
||||
{ hex: '#1c1917', number: 900 },
|
||||
{ hex: '#0c0a09', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Red',
|
||||
palettes: [
|
||||
{ hex: '#fef2f2', number: 50 },
|
||||
{ hex: '#fee2e2', number: 100 },
|
||||
{ hex: '#fecaca', number: 200 },
|
||||
{ hex: '#fca5a5', number: 300 },
|
||||
{ hex: '#f87171', number: 400 },
|
||||
{ hex: '#ef4444', number: 500 },
|
||||
{ hex: '#dc2626', number: 600 },
|
||||
{ hex: '#b91c1c', number: 700 },
|
||||
{ hex: '#991b1b', number: 800 },
|
||||
{ hex: '#7f1d1d', number: 900 },
|
||||
{ hex: '#450a0a', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Orange',
|
||||
palettes: [
|
||||
{ hex: '#fff7ed', number: 50 },
|
||||
{ hex: '#ffedd5', number: 100 },
|
||||
{ hex: '#fed7aa', number: 200 },
|
||||
{ hex: '#fdba74', number: 300 },
|
||||
{ hex: '#fb923c', number: 400 },
|
||||
{ hex: '#f97316', number: 500 },
|
||||
{ hex: '#ea580c', number: 600 },
|
||||
{ hex: '#c2410c', number: 700 },
|
||||
{ hex: '#9a3412', number: 800 },
|
||||
{ hex: '#7c2d12', number: 900 },
|
||||
{ hex: '#431407', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Amber',
|
||||
palettes: [
|
||||
{ hex: '#fffbeb', number: 50 },
|
||||
{ hex: '#fef3c7', number: 100 },
|
||||
{ hex: '#fde68a', number: 200 },
|
||||
{ hex: '#fcd34d', number: 300 },
|
||||
{ hex: '#fbbf24', number: 400 },
|
||||
{ hex: '#f59e0b', number: 500 },
|
||||
{ hex: '#d97706', number: 600 },
|
||||
{ hex: '#b45309', number: 700 },
|
||||
{ hex: '#92400e', number: 800 },
|
||||
{ hex: '#78350f', number: 900 },
|
||||
{ hex: '#451a03', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Yellow',
|
||||
palettes: [
|
||||
{ hex: '#fefce8', number: 50 },
|
||||
{ hex: '#fef9c3', number: 100 },
|
||||
{ hex: '#fef08a', number: 200 },
|
||||
{ hex: '#fde047', number: 300 },
|
||||
{ hex: '#facc15', number: 400 },
|
||||
{ hex: '#eab308', number: 500 },
|
||||
{ hex: '#ca8a04', number: 600 },
|
||||
{ hex: '#a16207', number: 700 },
|
||||
{ hex: '#854d0e', number: 800 },
|
||||
{ hex: '#713f12', number: 900 },
|
||||
{ hex: '#422006', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Lime',
|
||||
palettes: [
|
||||
{ hex: '#f7fee7', number: 50 },
|
||||
{ hex: '#ecfccb', number: 100 },
|
||||
{ hex: '#d9f99d', number: 200 },
|
||||
{ hex: '#bef264', number: 300 },
|
||||
{ hex: '#a3e635', number: 400 },
|
||||
{ hex: '#84cc16', number: 500 },
|
||||
{ hex: '#65a30d', number: 600 },
|
||||
{ hex: '#4d7c0f', number: 700 },
|
||||
{ hex: '#3f6212', number: 800 },
|
||||
{ hex: '#365314', number: 900 },
|
||||
{ hex: '#1a2e05', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Green',
|
||||
palettes: [
|
||||
{ hex: '#f0fdf4', number: 50 },
|
||||
{ hex: '#dcfce7', number: 100 },
|
||||
{ hex: '#bbf7d0', number: 200 },
|
||||
{ hex: '#86efac', number: 300 },
|
||||
{ hex: '#4ade80', number: 400 },
|
||||
{ hex: '#22c55e', number: 500 },
|
||||
{ hex: '#16a34a', number: 600 },
|
||||
{ hex: '#15803d', number: 700 },
|
||||
{ hex: '#166534', number: 800 },
|
||||
{ hex: '#14532d', number: 900 },
|
||||
{ hex: '#052e16', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Emerald',
|
||||
palettes: [
|
||||
{ hex: '#ecfdf5', number: 50 },
|
||||
{ hex: '#d1fae5', number: 100 },
|
||||
{ hex: '#a7f3d0', number: 200 },
|
||||
{ hex: '#6ee7b7', number: 300 },
|
||||
{ hex: '#34d399', number: 400 },
|
||||
{ hex: '#10b981', number: 500 },
|
||||
{ hex: '#059669', number: 600 },
|
||||
{ hex: '#047857', number: 700 },
|
||||
{ hex: '#065f46', number: 800 },
|
||||
{ hex: '#064e3b', number: 900 },
|
||||
{ hex: '#022c22', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Teal',
|
||||
palettes: [
|
||||
{ hex: '#f0fdfa', number: 50 },
|
||||
{ hex: '#ccfbf1', number: 100 },
|
||||
{ hex: '#99f6e4', number: 200 },
|
||||
{ hex: '#5eead4', number: 300 },
|
||||
{ hex: '#2dd4bf', number: 400 },
|
||||
{ hex: '#14b8a6', number: 500 },
|
||||
{ hex: '#0d9488', number: 600 },
|
||||
{ hex: '#0f766e', number: 700 },
|
||||
{ hex: '#115e59', number: 800 },
|
||||
{ hex: '#134e4a', number: 900 },
|
||||
{ hex: '#042f2e', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Cyan',
|
||||
palettes: [
|
||||
{ hex: '#ecfeff', number: 50 },
|
||||
{ hex: '#cffafe', number: 100 },
|
||||
{ hex: '#a5f3fc', number: 200 },
|
||||
{ hex: '#67e8f9', number: 300 },
|
||||
{ hex: '#22d3ee', number: 400 },
|
||||
{ hex: '#06b6d4', number: 500 },
|
||||
{ hex: '#0891b2', number: 600 },
|
||||
{ hex: '#0e7490', number: 700 },
|
||||
{ hex: '#155e75', number: 800 },
|
||||
{ hex: '#164e63', number: 900 },
|
||||
{ hex: '#083344', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Sky',
|
||||
palettes: [
|
||||
{ hex: '#f0f9ff', number: 50 },
|
||||
{ hex: '#e0f2fe', number: 100 },
|
||||
{ hex: '#bae6fd', number: 200 },
|
||||
{ hex: '#7dd3fc', number: 300 },
|
||||
{ hex: '#38bdf8', number: 400 },
|
||||
{ hex: '#0ea5e9', number: 500 },
|
||||
{ hex: '#0284c7', number: 600 },
|
||||
{ hex: '#0369a1', number: 700 },
|
||||
{ hex: '#075985', number: 800 },
|
||||
{ hex: '#0c4a6e', number: 900 },
|
||||
{ hex: '#082f49', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Blue',
|
||||
palettes: [
|
||||
{ hex: '#eff6ff', number: 50 },
|
||||
{ hex: '#dbeafe', number: 100 },
|
||||
{ hex: '#bfdbfe', number: 200 },
|
||||
{ hex: '#93c5fd', number: 300 },
|
||||
{ hex: '#60a5fa', number: 400 },
|
||||
{ hex: '#3b82f6', number: 500 },
|
||||
{ hex: '#2563eb', number: 600 },
|
||||
{ hex: '#1d4ed8', number: 700 },
|
||||
{ hex: '#1e40af', number: 800 },
|
||||
{ hex: '#1e3a8a', number: 900 },
|
||||
{ hex: '#172554', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Indigo',
|
||||
palettes: [
|
||||
{ hex: '#eef2ff', number: 50 },
|
||||
{ hex: '#e0e7ff', number: 100 },
|
||||
{ hex: '#c7d2fe', number: 200 },
|
||||
{ hex: '#a5b4fc', number: 300 },
|
||||
{ hex: '#818cf8', number: 400 },
|
||||
{ hex: '#6366f1', number: 500 },
|
||||
{ hex: '#4f46e5', number: 600 },
|
||||
{ hex: '#4338ca', number: 700 },
|
||||
{ hex: '#3730a3', number: 800 },
|
||||
{ hex: '#312e81', number: 900 },
|
||||
{ hex: '#1e1b4b', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Violet',
|
||||
palettes: [
|
||||
{ hex: '#f5f3ff', number: 50 },
|
||||
{ hex: '#ede9fe', number: 100 },
|
||||
{ hex: '#ddd6fe', number: 200 },
|
||||
{ hex: '#c4b5fd', number: 300 },
|
||||
{ hex: '#a78bfa', number: 400 },
|
||||
{ hex: '#8b5cf6', number: 500 },
|
||||
{ hex: '#7c3aed', number: 600 },
|
||||
{ hex: '#6d28d9', number: 700 },
|
||||
{ hex: '#5b21b6', number: 800 },
|
||||
{ hex: '#4c1d95', number: 900 },
|
||||
{ hex: '#2e1065', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Purple',
|
||||
palettes: [
|
||||
{ hex: '#faf5ff', number: 50 },
|
||||
{ hex: '#f3e8ff', number: 100 },
|
||||
{ hex: '#e9d5ff', number: 200 },
|
||||
{ hex: '#d8b4fe', number: 300 },
|
||||
{ hex: '#c084fc', number: 400 },
|
||||
{ hex: '#a855f7', number: 500 },
|
||||
{ hex: '#9333ea', number: 600 },
|
||||
{ hex: '#7e22ce', number: 700 },
|
||||
{ hex: '#6b21a8', number: 800 },
|
||||
{ hex: '#581c87', number: 900 },
|
||||
{ hex: '#3b0764', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Fuchsia',
|
||||
palettes: [
|
||||
{ hex: '#fdf4ff', number: 50 },
|
||||
{ hex: '#fae8ff', number: 100 },
|
||||
{ hex: '#f5d0fe', number: 200 },
|
||||
{ hex: '#f0abfc', number: 300 },
|
||||
{ hex: '#e879f9', number: 400 },
|
||||
{ hex: '#d946ef', number: 500 },
|
||||
{ hex: '#c026d3', number: 600 },
|
||||
{ hex: '#a21caf', number: 700 },
|
||||
{ hex: '#86198f', number: 800 },
|
||||
{ hex: '#701a75', number: 900 },
|
||||
{ hex: '#4a044e', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Pink',
|
||||
palettes: [
|
||||
{ hex: '#fdf2f8', number: 50 },
|
||||
{ hex: '#fce7f3', number: 100 },
|
||||
{ hex: '#fbcfe8', number: 200 },
|
||||
{ hex: '#f9a8d4', number: 300 },
|
||||
{ hex: '#f472b6', number: 400 },
|
||||
{ hex: '#ec4899', number: 500 },
|
||||
{ hex: '#db2777', number: 600 },
|
||||
{ hex: '#be185d', number: 700 },
|
||||
{ hex: '#9d174d', number: 800 },
|
||||
{ hex: '#831843', number: 900 },
|
||||
{ hex: '#500724', number: 950 }
|
||||
]
|
||||
},
|
||||
{
|
||||
name: 'Rose',
|
||||
palettes: [
|
||||
{ hex: '#fff1f2', number: 50 },
|
||||
{ hex: '#ffe4e6', number: 100 },
|
||||
{ hex: '#fecdd3', number: 200 },
|
||||
{ hex: '#fda4af', number: 300 },
|
||||
{ hex: '#fb7185', number: 400 },
|
||||
{ hex: '#f43f5e', number: 500 },
|
||||
{ hex: '#e11d48', number: 600 },
|
||||
{ hex: '#be123c', number: 700 },
|
||||
{ hex: '#9f1239', number: 800 },
|
||||
{ hex: '#881337', number: 900 },
|
||||
{ hex: '#4c0519', number: 950 }
|
||||
]
|
||||
}
|
||||
];
|
|
@ -0,0 +1,7 @@
|
|||
import { colorPalettes } from './constant';
|
||||
|
||||
export * from './palette';
|
||||
export * from './shared';
|
||||
export { colorPalettes };
|
||||
|
||||
export * from './types';
|
|
@ -0,0 +1,176 @@
|
|||
import type { AnyColor, HsvColor } from 'colord';
|
||||
import { getHex, getHsv, isValidColor, mixColor } from '../shared';
|
||||
import type { ColorIndex } from '../types';
|
||||
|
||||
/** Hue step */
|
||||
const hueStep = 2;
|
||||
/** Saturation step, light color part */
|
||||
const saturationStep = 16;
|
||||
/** Saturation step, dark color part */
|
||||
const saturationStep2 = 5;
|
||||
/** Brightness step, light color part */
|
||||
const brightnessStep1 = 5;
|
||||
/** Brightness step, dark color part */
|
||||
const brightnessStep2 = 15;
|
||||
/** Light color count, main color up */
|
||||
const lightColorCount = 5;
|
||||
/** Dark color count, main color down */
|
||||
const darkColorCount = 4;
|
||||
|
||||
/**
|
||||
* Get AntD palette color by index
|
||||
*
|
||||
* @param color - Color
|
||||
* @param index - The color index of color palette (the main color index is 6)
|
||||
* @returns Hex color
|
||||
*/
|
||||
export function getAntDPaletteColorByIndex(color: AnyColor, index: ColorIndex): string {
|
||||
if (!isValidColor(color)) {
|
||||
throw new Error('invalid input color value');
|
||||
}
|
||||
|
||||
if (index === 6) {
|
||||
return getHex(color);
|
||||
}
|
||||
|
||||
const isLight = index < 6;
|
||||
const hsv = getHsv(color);
|
||||
const i = isLight ? lightColorCount + 1 - index : index - lightColorCount - 1;
|
||||
|
||||
const newHsv: HsvColor = {
|
||||
h: getHue(hsv, i, isLight),
|
||||
s: getSaturation(hsv, i, isLight),
|
||||
v: getValue(hsv, i, isLight)
|
||||
};
|
||||
|
||||
return getHex(newHsv);
|
||||
}
|
||||
|
||||
/** Map of dark color index and opacity */
|
||||
const darkColorMap = [
|
||||
{ index: 7, opacity: 0.15 },
|
||||
{ index: 6, opacity: 0.25 },
|
||||
{ index: 5, opacity: 0.3 },
|
||||
{ index: 5, opacity: 0.45 },
|
||||
{ index: 5, opacity: 0.65 },
|
||||
{ index: 5, opacity: 0.85 },
|
||||
{ index: 5, opacity: 0.9 },
|
||||
{ index: 4, opacity: 0.93 },
|
||||
{ index: 3, opacity: 0.95 },
|
||||
{ index: 2, opacity: 0.97 },
|
||||
{ index: 1, opacity: 0.98 }
|
||||
];
|
||||
|
||||
/**
|
||||
* Get AntD color palette
|
||||
*
|
||||
* @param color - Color
|
||||
* @param darkTheme - Dark theme
|
||||
* @param darkThemeMixColor - Dark theme mix color (default: #141414)
|
||||
*/
|
||||
export function getAntDColorPalette(color: AnyColor, darkTheme = false, darkThemeMixColor = '#141414'): string[] {
|
||||
const indexes: ColorIndex[] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
||||
|
||||
const patterns = indexes.map(index => getAntDPaletteColorByIndex(color, index));
|
||||
|
||||
if (darkTheme) {
|
||||
const darkPatterns = darkColorMap.map(({ index, opacity }) => {
|
||||
const darkColor = mixColor(darkThemeMixColor, patterns[index], opacity);
|
||||
|
||||
return darkColor;
|
||||
});
|
||||
|
||||
return darkPatterns.map(item => getHex(item));
|
||||
}
|
||||
|
||||
return patterns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hue
|
||||
*
|
||||
* @param hsv - Hsv format color
|
||||
* @param i - The relative distance from 6
|
||||
* @param isLight - Is light color
|
||||
*/
|
||||
function getHue(hsv: HsvColor, i: number, isLight: boolean) {
|
||||
let hue: number;
|
||||
|
||||
const hsvH = Math.round(hsv.h);
|
||||
|
||||
if (hsvH >= 60 && hsvH <= 240) {
|
||||
hue = isLight ? hsvH - hueStep * i : hsvH + hueStep * i;
|
||||
} else {
|
||||
hue = isLight ? hsvH + hueStep * i : hsvH - hueStep * i;
|
||||
}
|
||||
|
||||
if (hue < 0) {
|
||||
hue += 360;
|
||||
}
|
||||
|
||||
if (hue >= 360) {
|
||||
hue -= 360;
|
||||
}
|
||||
|
||||
return hue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get saturation
|
||||
*
|
||||
* @param hsv - Hsv format color
|
||||
* @param i - The relative distance from 6
|
||||
* @param isLight - Is light color
|
||||
*/
|
||||
function getSaturation(hsv: HsvColor, i: number, isLight: boolean) {
|
||||
if (hsv.h === 0 && hsv.s === 0) {
|
||||
return hsv.s;
|
||||
}
|
||||
|
||||
let saturation: number;
|
||||
|
||||
if (isLight) {
|
||||
saturation = hsv.s - saturationStep * i;
|
||||
} else if (i === darkColorCount) {
|
||||
saturation = hsv.s + saturationStep;
|
||||
} else {
|
||||
saturation = hsv.s + saturationStep2 * i;
|
||||
}
|
||||
|
||||
if (saturation > 100) {
|
||||
saturation = 100;
|
||||
}
|
||||
|
||||
if (isLight && i === lightColorCount && saturation > 10) {
|
||||
saturation = 10;
|
||||
}
|
||||
|
||||
if (saturation < 6) {
|
||||
saturation = 6;
|
||||
}
|
||||
|
||||
return saturation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value of hsv
|
||||
*
|
||||
* @param hsv - Hsv format color
|
||||
* @param i - The relative distance from 6
|
||||
* @param isLight - Is light color
|
||||
*/
|
||||
function getValue(hsv: HsvColor, i: number, isLight: boolean) {
|
||||
let value: number;
|
||||
|
||||
if (isLight) {
|
||||
value = hsv.v + brightnessStep1 * i;
|
||||
} else {
|
||||
value = hsv.v - brightnessStep2 * i;
|
||||
}
|
||||
|
||||
if (value > 100) {
|
||||
value = 100;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
import type { AnyColor } from 'colord';
|
||||
import { getHex } from '../shared';
|
||||
import type { ColorPaletteNumber } from '../types';
|
||||
import { getRecommendedColorPalette } from './recommend';
|
||||
import { getAntDColorPalette } from './antd';
|
||||
|
||||
/**
|
||||
* get color palette by provided color
|
||||
*
|
||||
* @param color
|
||||
* @param recommended whether to get recommended color palette (the provided color may not be the main color)
|
||||
*/
|
||||
export function getColorPalette(color: AnyColor, recommended = false) {
|
||||
const colorMap = new Map<ColorPaletteNumber, string>();
|
||||
|
||||
if (recommended) {
|
||||
const colorPalette = getRecommendedColorPalette(getHex(color));
|
||||
colorPalette.palettes.forEach(palette => {
|
||||
colorMap.set(palette.number, palette.hex);
|
||||
});
|
||||
} else {
|
||||
const colors = getAntDColorPalette(color);
|
||||
|
||||
const colorNumbers: ColorPaletteNumber[] = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950];
|
||||
|
||||
colorNumbers.forEach((number, index) => {
|
||||
colorMap.set(number, colors[index]);
|
||||
});
|
||||
}
|
||||
|
||||
return colorMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* get color palette color by number
|
||||
*
|
||||
* @param color the provided color
|
||||
* @param number the color palette number
|
||||
* @param recommended whether to get recommended color palette (the provided color may not be the main color)
|
||||
*/
|
||||
export function getPaletteColorByNumber(color: AnyColor, number: ColorPaletteNumber, recommended = false) {
|
||||
const colorMap = getColorPalette(color, recommended);
|
||||
|
||||
return colorMap.get(number as ColorPaletteNumber)!;
|
||||
}
|
|
@ -0,0 +1,152 @@
|
|||
import { getColorName, getDeltaE, getHsl, isValidColor, transformHslToHex } from '../shared';
|
||||
import { colorPalettes } from '../constant';
|
||||
import type {
|
||||
ColorPalette,
|
||||
ColorPaletteFamily,
|
||||
ColorPaletteFamilyWithNearestPalette,
|
||||
ColorPaletteMatch,
|
||||
ColorPaletteNumber
|
||||
} from '../types';
|
||||
|
||||
/**
|
||||
* get recommended color palette by provided color
|
||||
*
|
||||
* @param color the provided color
|
||||
*/
|
||||
export function getRecommendedColorPalette(color: string) {
|
||||
const colorPaletteFamily = getRecommendedColorPaletteFamily(color);
|
||||
|
||||
const colorMap = new Map<ColorPaletteNumber, ColorPalette>();
|
||||
|
||||
colorPaletteFamily.palettes.forEach(palette => {
|
||||
colorMap.set(palette.number, palette);
|
||||
});
|
||||
|
||||
const mainColor = colorMap.get(500)!;
|
||||
const matchColor = colorPaletteFamily.palettes.find(palette => palette.hex === color)!;
|
||||
|
||||
const colorPalette: ColorPaletteMatch = {
|
||||
...colorPaletteFamily,
|
||||
colorMap,
|
||||
main: mainColor,
|
||||
match: matchColor
|
||||
};
|
||||
|
||||
return colorPalette;
|
||||
}
|
||||
|
||||
/**
|
||||
* get recommended palette color by provided color
|
||||
*
|
||||
* @param color the provided color
|
||||
* @param number the color palette number
|
||||
*/
|
||||
export function getRecommendedPaletteColorByNumber(color: string, number: ColorPaletteNumber) {
|
||||
const colorPalette = getRecommendedColorPalette(color);
|
||||
|
||||
const { hex } = colorPalette.colorMap.get(number)!;
|
||||
|
||||
return hex;
|
||||
}
|
||||
|
||||
/**
|
||||
* get color palette family by provided color and color name
|
||||
*
|
||||
* @param color the provided color
|
||||
*/
|
||||
export function getRecommendedColorPaletteFamily(color: string) {
|
||||
if (!isValidColor(color)) {
|
||||
throw new Error('Invalid color, please check color value!');
|
||||
}
|
||||
|
||||
let colorName = getColorName(color);
|
||||
|
||||
colorName = colorName.toLowerCase().replace(/\s/g, '-');
|
||||
|
||||
const { h: h1, s: s1 } = getHsl(color);
|
||||
|
||||
const { nearestLightnessPalette, palettes } = getNearestColorPaletteFamily(color, colorPalettes);
|
||||
|
||||
const { number, hex } = nearestLightnessPalette;
|
||||
|
||||
const { h: h2, s: s2 } = getHsl(hex);
|
||||
|
||||
const deltaH = h1 - h2;
|
||||
|
||||
const sRatio = s1 / s2;
|
||||
|
||||
const colorPaletteFamily: ColorPaletteFamily = {
|
||||
name: colorName,
|
||||
palettes: palettes.map(palette => {
|
||||
let hexValue = color;
|
||||
|
||||
const isSame = number === palette.number;
|
||||
|
||||
if (!isSame) {
|
||||
const { h: h3, s: s3, l } = getHsl(palette.hex);
|
||||
|
||||
const newH = deltaH < 0 ? h3 + deltaH : h3 - deltaH;
|
||||
const newS = s3 * sRatio;
|
||||
|
||||
hexValue = transformHslToHex({
|
||||
h: newH,
|
||||
s: newS,
|
||||
l
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
hex: hexValue,
|
||||
number: palette.number
|
||||
};
|
||||
})
|
||||
};
|
||||
|
||||
return colorPaletteFamily;
|
||||
}
|
||||
|
||||
/**
|
||||
* get nearest color palette family
|
||||
*
|
||||
* @param color color
|
||||
* @param families color palette families
|
||||
*/
|
||||
function getNearestColorPaletteFamily(color: string, families: ColorPaletteFamily[]) {
|
||||
const familyWithConfig = families.map(family => {
|
||||
const palettes = family.palettes.map(palette => {
|
||||
return {
|
||||
...palette,
|
||||
delta: getDeltaE(color, palette.hex)
|
||||
};
|
||||
});
|
||||
|
||||
const nearestPalette = palettes.reduce((prev, curr) => (prev.delta < curr.delta ? prev : curr));
|
||||
|
||||
return {
|
||||
...family,
|
||||
palettes,
|
||||
nearestPalette
|
||||
};
|
||||
});
|
||||
|
||||
const nearestPaletteFamily = familyWithConfig.reduce((prev, curr) =>
|
||||
prev.nearestPalette.delta < curr.nearestPalette.delta ? prev : curr
|
||||
);
|
||||
|
||||
const { l } = getHsl(color);
|
||||
|
||||
const paletteFamily: ColorPaletteFamilyWithNearestPalette = {
|
||||
...nearestPaletteFamily,
|
||||
nearestLightnessPalette: nearestPaletteFamily.palettes.reduce((prev, curr) => {
|
||||
const { l: prevLightness } = getHsl(prev.hex);
|
||||
const { l: currLightness } = getHsl(curr.hex);
|
||||
|
||||
const deltaPrev = Math.abs(prevLightness - l);
|
||||
const deltaCurr = Math.abs(currLightness - l);
|
||||
|
||||
return deltaPrev < deltaCurr ? prev : curr;
|
||||
})
|
||||
};
|
||||
|
||||
return paletteFamily;
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
import { colord, extend } from 'colord';
|
||||
import namesPlugin from 'colord/plugins/names';
|
||||
import mixPlugin from 'colord/plugins/mix';
|
||||
import labPlugin from 'colord/plugins/lab';
|
||||
import type { AnyColor, HslColor, RgbColor } from 'colord';
|
||||
|
||||
extend([namesPlugin, mixPlugin, labPlugin]);
|
||||
|
||||
export function isValidColor(color: AnyColor) {
|
||||
return colord(color).isValid();
|
||||
}
|
||||
|
||||
export function getHex(color: AnyColor) {
|
||||
return colord(color).toHex();
|
||||
}
|
||||
|
||||
export function getRgb(color: AnyColor) {
|
||||
return colord(color).toRgb();
|
||||
}
|
||||
|
||||
export function getHsl(color: AnyColor) {
|
||||
return colord(color).toHsl();
|
||||
}
|
||||
|
||||
export function getHsv(color: AnyColor) {
|
||||
return colord(color).toHsv();
|
||||
}
|
||||
|
||||
export function getDeltaE(color1: AnyColor, color2: AnyColor) {
|
||||
return colord(color1).delta(color2);
|
||||
}
|
||||
|
||||
export function transformHslToHex(color: HslColor) {
|
||||
return colord(color).toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add color alpha
|
||||
*
|
||||
* @param color - Color
|
||||
* @param alpha - Alpha (0 - 1)
|
||||
*/
|
||||
export function addColorAlpha(color: AnyColor, alpha: number) {
|
||||
return colord(color).alpha(alpha).toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mix color
|
||||
*
|
||||
* @param firstColor - First color
|
||||
* @param secondColor - Second color
|
||||
* @param ratio - The ratio of the second color (0 - 1)
|
||||
*/
|
||||
export function mixColor(firstColor: AnyColor, secondColor: AnyColor, ratio: number) {
|
||||
return colord(firstColor).mix(secondColor, ratio).toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform color with opacity to similar color without opacity
|
||||
*
|
||||
* @param color - Color
|
||||
* @param alpha - Alpha (0 - 1)
|
||||
* @param bgColor Background color (usually white or black)
|
||||
*/
|
||||
export function transformColorWithOpacity(color: AnyColor, alpha: number, bgColor = '#ffffff') {
|
||||
const originColor = addColorAlpha(color, alpha);
|
||||
const { r: oR, g: oG, b: oB } = colord(originColor).toRgb();
|
||||
|
||||
const { r: bgR, g: bgG, b: bgB } = colord(bgColor).toRgb();
|
||||
|
||||
function calRgb(or: number, bg: number, al: number) {
|
||||
return bg + (or - bg) * al;
|
||||
}
|
||||
|
||||
const resultRgb: RgbColor = {
|
||||
r: calRgb(oR, bgR, alpha),
|
||||
g: calRgb(oG, bgG, alpha),
|
||||
b: calRgb(oB, bgB, alpha)
|
||||
};
|
||||
|
||||
return colord(resultRgb).toHex();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is white color
|
||||
*
|
||||
* @param color - Color
|
||||
*/
|
||||
export function isWhiteColor(color: AnyColor) {
|
||||
return colord(color).isEqual('#ffffff');
|
||||
}
|
||||
|
||||
export { colord };
|
|
@ -0,0 +1,2 @@
|
|||
export * from './colord';
|
||||
export * from './name';
|
|
@ -0,0 +1,49 @@
|
|||
import { colorNames } from '../constant';
|
||||
import { getHex, getHsl, getRgb } from './colord';
|
||||
|
||||
/**
|
||||
* Get color name
|
||||
*
|
||||
* @param color
|
||||
*/
|
||||
export function getColorName(color: string) {
|
||||
const hex = getHex(color);
|
||||
const rgb = getRgb(color);
|
||||
const hsl = getHsl(color);
|
||||
|
||||
let ndf = 0;
|
||||
let ndf1 = 0;
|
||||
let ndf2 = 0;
|
||||
let cl = -1;
|
||||
let df = -1;
|
||||
|
||||
let name = '';
|
||||
|
||||
colorNames.some((item, index) => {
|
||||
const [hexValue, colorName] = item;
|
||||
|
||||
const match = hex === hexValue;
|
||||
|
||||
if (match) {
|
||||
name = colorName;
|
||||
} else {
|
||||
const { r, g, b } = getRgb(hexValue);
|
||||
const { h, s, l } = getHsl(hexValue);
|
||||
|
||||
ndf1 = (rgb.r - r) ** 2 + (rgb.g - g) ** 2 + (rgb.b - b) ** 2;
|
||||
ndf2 = (hsl.h - h) ** 2 + (hsl.s - s) ** 2 + (hsl.l - l) ** 2;
|
||||
|
||||
ndf = ndf1 + ndf2 * 2;
|
||||
if (df < 0 || df > ndf) {
|
||||
df = ndf;
|
||||
cl = index;
|
||||
}
|
||||
}
|
||||
|
||||
return match;
|
||||
});
|
||||
|
||||
name = colorNames[cl][1];
|
||||
|
||||
return name;
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/**
|
||||
* the color palette number
|
||||
*
|
||||
* the main color number is 500
|
||||
*/
|
||||
export type ColorPaletteNumber = 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950;
|
||||
|
||||
/** the color palette */
|
||||
export type ColorPalette = {
|
||||
/** the color hex value */
|
||||
hex: string;
|
||||
/**
|
||||
* the color number
|
||||
*
|
||||
* - 50 | 100 | 200 | 300 | 400 | 500 | 600 | 700 | 800 | 900 | 950
|
||||
*/
|
||||
number: ColorPaletteNumber;
|
||||
};
|
||||
|
||||
/** the color palette family */
|
||||
export type ColorPaletteFamily = {
|
||||
/** the color palette family name */
|
||||
name: string;
|
||||
/** the color palettes */
|
||||
palettes: ColorPalette[];
|
||||
};
|
||||
|
||||
/** the color palette with delta */
|
||||
export type ColorPaletteWithDelta = ColorPalette & {
|
||||
delta: number;
|
||||
};
|
||||
|
||||
/** the color palette family with nearest palette */
|
||||
export type ColorPaletteFamilyWithNearestPalette = ColorPaletteFamily & {
|
||||
nearestPalette: ColorPaletteWithDelta;
|
||||
nearestLightnessPalette: ColorPaletteWithDelta;
|
||||
};
|
||||
|
||||
/** the color palette match */
|
||||
export type ColorPaletteMatch = ColorPaletteFamily & {
|
||||
/** the color map of the palette */
|
||||
colorMap: Map<ColorPaletteNumber, ColorPalette>;
|
||||
/**
|
||||
* the main color of the palette
|
||||
*
|
||||
* which number is 500
|
||||
*/
|
||||
main: ColorPalette;
|
||||
/** the match color of the palette */
|
||||
match: ColorPalette;
|
||||
};
|
||||
|
||||
/**
|
||||
* The color index of color palette
|
||||
*
|
||||
* From left to right, the color is from light to dark, 6 is main color
|
||||
*/
|
||||
export type ColorIndex = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11;
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"jsx": "preserve",
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"noUnusedLocals": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "@sa/hooks",
|
||||
"version": "1.1.1",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@sa/axios": "workspace:*"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"jsx": "preserve",
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"noUnusedLocals": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "@sa/hooks",
|
||||
"version": "1.1.1",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@sa/axios": "workspace:*"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
import useBoolean from './use-boolean';
|
||||
import useLoading from './use-loading';
|
||||
import useCountDown from './use-count-down';
|
||||
import useContext from './use-context';
|
||||
import useSvgIconRender from './use-svg-icon-render';
|
||||
import useHookTable from './use-table';
|
||||
|
||||
export { useBoolean, useLoading, useCountDown, useContext, useSvgIconRender, useHookTable };
|
||||
|
||||
export * from './use-signal';
|
||||
export * from './use-table';
|
|
@ -0,0 +1,31 @@
|
|||
import { ref } from 'vue';
|
||||
|
||||
/**
|
||||
* Boolean
|
||||
*
|
||||
* @param initValue Init value
|
||||
*/
|
||||
export default function useBoolean(initValue = false) {
|
||||
const bool = ref(initValue);
|
||||
|
||||
function setBool(value: boolean) {
|
||||
bool.value = value;
|
||||
}
|
||||
function setTrue() {
|
||||
setBool(true);
|
||||
}
|
||||
function setFalse() {
|
||||
setBool(false);
|
||||
}
|
||||
function toggle() {
|
||||
setBool(!bool.value);
|
||||
}
|
||||
|
||||
return {
|
||||
bool,
|
||||
setBool,
|
||||
setTrue,
|
||||
setFalse,
|
||||
toggle
|
||||
};
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
import { inject, provide } from 'vue';
|
||||
import type { InjectionKey } from 'vue';
|
||||
|
||||
/**
|
||||
* Use context
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // there are three vue files: A.vue, B.vue, C.vue, and A.vue is the parent component of B.vue and C.vue
|
||||
*
|
||||
* // context.ts
|
||||
* import { ref } from 'vue';
|
||||
* import { useContext } from '@sa/hooks';
|
||||
*
|
||||
* export const { setupStore, useStore } = useContext('demo', () => {
|
||||
* const count = ref(0);
|
||||
*
|
||||
* function increment() {
|
||||
* count.value++;
|
||||
* }
|
||||
*
|
||||
* function decrement() {
|
||||
* count.value--;
|
||||
* }
|
||||
*
|
||||
* return {
|
||||
* count,
|
||||
* increment,
|
||||
* decrement
|
||||
* };
|
||||
* })
|
||||
* ``` // A.vue
|
||||
* ```vue
|
||||
* <template>
|
||||
* <div>A</div>
|
||||
* </template>
|
||||
* <script setup lang="ts">
|
||||
* import { setupStore } from './context';
|
||||
*
|
||||
* setupStore();
|
||||
* // const { increment } = setupStore(); // also can control the store in the parent component
|
||||
* </script>
|
||||
* ``` // B.vue
|
||||
* ```vue
|
||||
* <template>
|
||||
* <div>B</div>
|
||||
* </template>
|
||||
* <script setup lang="ts">
|
||||
* import { useStore } from './context';
|
||||
*
|
||||
* const { count, increment } = useStore();
|
||||
* </script>
|
||||
* ```;
|
||||
*
|
||||
* // C.vue is same as B.vue
|
||||
*
|
||||
* @param contextName Context name
|
||||
* @param fn Context function
|
||||
*/
|
||||
export default function useContext<T extends (...args: any[]) => any>(contextName: string, fn: T) {
|
||||
type Context = ReturnType<T>;
|
||||
|
||||
const { useProvide, useInject: useStore } = createContext<Context>(contextName);
|
||||
|
||||
function setupStore(...args: Parameters<T>) {
|
||||
const context: Context = fn(...args);
|
||||
return useProvide(context);
|
||||
}
|
||||
|
||||
return {
|
||||
/** Setup store in the parent component */
|
||||
setupStore,
|
||||
/** Use store in the child component */
|
||||
useStore
|
||||
};
|
||||
}
|
||||
|
||||
/** Create context */
|
||||
function createContext<T>(contextName: string) {
|
||||
const injectKey: InjectionKey<T> = Symbol(contextName);
|
||||
|
||||
function useProvide(context: T) {
|
||||
provide(injectKey, context);
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
function useInject() {
|
||||
return inject(injectKey) as T;
|
||||
}
|
||||
|
||||
return {
|
||||
useProvide,
|
||||
useInject
|
||||
};
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
import { computed, onScopeDispose, ref } from 'vue';
|
||||
import { useRafFn } from '@vueuse/core';
|
||||
|
||||
/**
|
||||
* count down
|
||||
*
|
||||
* @param seconds - count down seconds
|
||||
*/
|
||||
export default function useCountDown(seconds: number) {
|
||||
const FPS_PER_SECOND = 60;
|
||||
|
||||
const fps = ref(0);
|
||||
|
||||
const count = computed(() => Math.ceil(fps.value / FPS_PER_SECOND));
|
||||
|
||||
const isCounting = computed(() => fps.value > 0);
|
||||
|
||||
const { pause, resume } = useRafFn(
|
||||
() => {
|
||||
if (fps.value > 0) {
|
||||
fps.value -= 1;
|
||||
} else {
|
||||
pause();
|
||||
}
|
||||
},
|
||||
{ immediate: false }
|
||||
);
|
||||
|
||||
function start(updateSeconds: number = seconds) {
|
||||
fps.value = FPS_PER_SECOND * updateSeconds;
|
||||
resume();
|
||||
}
|
||||
|
||||
function stop() {
|
||||
fps.value = 0;
|
||||
pause();
|
||||
}
|
||||
|
||||
onScopeDispose(() => {
|
||||
pause();
|
||||
});
|
||||
|
||||
return {
|
||||
count,
|
||||
isCounting,
|
||||
start,
|
||||
stop
|
||||
};
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
import useBoolean from './use-boolean';
|
||||
|
||||
/**
|
||||
* Loading
|
||||
*
|
||||
* @param initValue Init value
|
||||
*/
|
||||
export default function useLoading(initValue = false) {
|
||||
const { bool: loading, setTrue: startLoading, setFalse: endLoading } = useBoolean(initValue);
|
||||
|
||||
return {
|
||||
loading,
|
||||
startLoading,
|
||||
endLoading
|
||||
};
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
import { computed, ref, shallowRef, triggerRef } from 'vue';
|
||||
import type {
|
||||
ComputedGetter,
|
||||
DebuggerOptions,
|
||||
Ref,
|
||||
ShallowRef,
|
||||
WritableComputedOptions,
|
||||
WritableComputedRef
|
||||
} from 'vue';
|
||||
|
||||
type Updater<T> = (value: T) => T;
|
||||
type Mutator<T> = (value: T) => void;
|
||||
|
||||
/**
|
||||
* Signal is a reactive value that can be set, updated or mutated
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const count = useSignal(0);
|
||||
*
|
||||
* // `watchEffect`
|
||||
* watchEffect(() => {
|
||||
* console.log(count());
|
||||
* });
|
||||
*
|
||||
* // watch
|
||||
* watch(count, value => {
|
||||
* console.log(value);
|
||||
* });
|
||||
*
|
||||
* // useComputed
|
||||
* const double = useComputed(() => count() * 2);
|
||||
* const writeableDouble = useComputed({
|
||||
* get: () => count() * 2,
|
||||
* set: value => count.set(value / 2)
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export interface Signal<T> {
|
||||
(): Readonly<T>;
|
||||
/**
|
||||
* Set the value of the signal
|
||||
*
|
||||
* It recommend use `set` for primitive values
|
||||
*
|
||||
* @param value
|
||||
*/
|
||||
set(value: T): void;
|
||||
/**
|
||||
* Update the value of the signal using an updater function
|
||||
*
|
||||
* It recommend use `update` for non-primitive values, only the first level of the object will be reactive.
|
||||
*
|
||||
* @param updater
|
||||
*/
|
||||
update(updater: Updater<T>): void;
|
||||
/**
|
||||
* Mutate the value of the signal using a mutator function
|
||||
*
|
||||
* this action will call `triggerRef`, so the value will be tracked on `watchEffect`.
|
||||
*
|
||||
* It recommend use `mutate` for non-primitive values, all levels of the object will be reactive.
|
||||
*
|
||||
* @param mutator
|
||||
*/
|
||||
mutate(mutator: Mutator<T>): void;
|
||||
/**
|
||||
* Get the reference of the signal
|
||||
*
|
||||
* Sometimes it can be useful to make `v-model` work with the signal
|
||||
*
|
||||
* ```vue
|
||||
* <template>
|
||||
* <input v-model="model.count" />
|
||||
* </template>;
|
||||
*
|
||||
* <script setup lang="ts">
|
||||
* const state = useSignal({ count: 0 }, { useRef: true });
|
||||
*
|
||||
* const model = state.getRef();
|
||||
* </script>
|
||||
* ```
|
||||
*/
|
||||
getRef(): Readonly<ShallowRef<Readonly<T>>>;
|
||||
}
|
||||
|
||||
export interface ReadonlySignal<T> {
|
||||
(): Readonly<T>;
|
||||
}
|
||||
|
||||
export interface SignalOptions {
|
||||
/**
|
||||
* Whether to use `ref` to store the value
|
||||
*
|
||||
* @default false use `sharedRef` to store the value
|
||||
*/
|
||||
useRef?: boolean;
|
||||
}
|
||||
|
||||
export function useSignal<T>(initialValue: T, options?: SignalOptions): Signal<T> {
|
||||
const { useRef } = options || {};
|
||||
|
||||
const state = useRef ? (ref(initialValue) as Ref<T>) : shallowRef(initialValue);
|
||||
|
||||
return createSignal(state);
|
||||
}
|
||||
|
||||
export function useComputed<T>(getter: ComputedGetter<T>, debugOptions?: DebuggerOptions): ReadonlySignal<T>;
|
||||
export function useComputed<T>(options: WritableComputedOptions<T>, debugOptions?: DebuggerOptions): Signal<T>;
|
||||
export function useComputed<T>(
|
||||
getterOrOptions: ComputedGetter<T> | WritableComputedOptions<T>,
|
||||
debugOptions?: DebuggerOptions
|
||||
) {
|
||||
const isGetter = typeof getterOrOptions === 'function';
|
||||
|
||||
const computedValue = computed(getterOrOptions as any, debugOptions);
|
||||
|
||||
if (isGetter) {
|
||||
return () => computedValue.value as ReadonlySignal<T>;
|
||||
}
|
||||
|
||||
return createSignal(computedValue);
|
||||
}
|
||||
|
||||
function createSignal<T>(state: ShallowRef<T> | WritableComputedRef<T>): Signal<T> {
|
||||
const signal = () => state.value;
|
||||
|
||||
signal.set = (value: T) => {
|
||||
state.value = value;
|
||||
};
|
||||
|
||||
signal.update = (updater: Updater<T>) => {
|
||||
state.value = updater(state.value);
|
||||
};
|
||||
|
||||
signal.mutate = (mutator: Mutator<T>) => {
|
||||
mutator(state.value);
|
||||
triggerRef(state);
|
||||
};
|
||||
|
||||
signal.getRef = () => state as Readonly<ShallowRef<Readonly<T>>>;
|
||||
|
||||
return signal;
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
import { h } from 'vue';
|
||||
import type { Component } from 'vue';
|
||||
|
||||
/**
|
||||
* Svg icon render hook
|
||||
*
|
||||
* @param SvgIcon Svg icon component
|
||||
*/
|
||||
export default function useSvgIconRender(SvgIcon: Component) {
|
||||
interface IconConfig {
|
||||
/** Iconify icon name */
|
||||
icon?: string;
|
||||
/** Local icon name */
|
||||
localIcon?: string;
|
||||
/** Icon color */
|
||||
color?: string;
|
||||
/** Icon size */
|
||||
fontSize?: number;
|
||||
}
|
||||
|
||||
type IconStyle = Partial<Pick<CSSStyleDeclaration, 'color' | 'fontSize'>>;
|
||||
|
||||
/**
|
||||
* Svg icon VNode
|
||||
*
|
||||
* @param config
|
||||
*/
|
||||
const SvgIconVNode = (config: IconConfig) => {
|
||||
const { color, fontSize, icon, localIcon } = config;
|
||||
|
||||
const style: IconStyle = {};
|
||||
|
||||
if (color) {
|
||||
style.color = color;
|
||||
}
|
||||
if (fontSize) {
|
||||
style.fontSize = `${fontSize}px`;
|
||||
}
|
||||
|
||||
if (!icon && !localIcon) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return () => h(SvgIcon, { icon, localIcon, style });
|
||||
};
|
||||
|
||||
return {
|
||||
SvgIconVNode
|
||||
};
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
import { computed, reactive, ref } from 'vue';
|
||||
import type { Ref } from 'vue';
|
||||
import useBoolean from './use-boolean';
|
||||
import useLoading from './use-loading';
|
||||
|
||||
export type MaybePromise<T> = T | Promise<T>;
|
||||
|
||||
export type ApiFn = (args: any) => Promise<unknown>;
|
||||
|
||||
export type TableColumnCheck = {
|
||||
key: string;
|
||||
title: string;
|
||||
checked: boolean;
|
||||
};
|
||||
|
||||
export type TableDataWithIndex<T> = T & { index: number };
|
||||
|
||||
export type TransformedData<T> = {
|
||||
data: TableDataWithIndex<T>[];
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
total: number;
|
||||
};
|
||||
|
||||
export type Transformer<T, Response> = (response: Response) => TransformedData<T>;
|
||||
|
||||
export type TableConfig<A extends ApiFn, T, C> = {
|
||||
/** api function to get table data */
|
||||
apiFn: A;
|
||||
/** api params */
|
||||
apiParams?: Parameters<A>[0];
|
||||
/** transform api response to table data */
|
||||
transformer: Transformer<T, Awaited<ReturnType<A>>>;
|
||||
/** columns factory */
|
||||
columns: () => C[];
|
||||
/**
|
||||
* get column checks
|
||||
*
|
||||
* @param columns
|
||||
*/
|
||||
getColumnChecks: (columns: C[]) => TableColumnCheck[];
|
||||
/**
|
||||
* get columns
|
||||
*
|
||||
* @param columns
|
||||
*/
|
||||
getColumns: (columns: C[], checks: TableColumnCheck[]) => C[];
|
||||
/**
|
||||
* callback when response fetched
|
||||
*
|
||||
* @param transformed transformed data
|
||||
*/
|
||||
onFetched?: (transformed: TransformedData<T>) => MaybePromise<void>;
|
||||
/**
|
||||
* whether to get data immediately
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
immediate?: boolean;
|
||||
};
|
||||
|
||||
export default function useHookTable<A extends ApiFn, T, C>(config: TableConfig<A, T, C>) {
|
||||
const { loading, startLoading, endLoading } = useLoading();
|
||||
const { bool: empty, setBool: setEmpty } = useBoolean();
|
||||
|
||||
const { apiFn, apiParams, transformer, immediate = true, getColumnChecks, getColumns } = config;
|
||||
|
||||
const searchParams: NonNullable<Parameters<A>[0]> = reactive({ ...apiParams });
|
||||
|
||||
const allColumns = ref(config.columns()) as Ref<C[]>;
|
||||
|
||||
const data: Ref<T[]> = ref([]);
|
||||
|
||||
const columnChecks: Ref<TableColumnCheck[]> = ref(getColumnChecks(config.columns()));
|
||||
|
||||
const columns = computed(() => getColumns(allColumns.value, columnChecks.value));
|
||||
|
||||
function reloadColumns() {
|
||||
allColumns.value = config.columns();
|
||||
|
||||
const checkMap = new Map(columnChecks.value.map(col => [col.key, col.checked]));
|
||||
|
||||
const defaultChecks = getColumnChecks(allColumns.value);
|
||||
|
||||
columnChecks.value = defaultChecks.map(col => ({
|
||||
...col,
|
||||
checked: checkMap.get(col.key) ?? col.checked
|
||||
}));
|
||||
}
|
||||
|
||||
async function getData() {
|
||||
startLoading();
|
||||
|
||||
const formattedParams = formatSearchParams(searchParams);
|
||||
|
||||
const response = await apiFn(formattedParams);
|
||||
|
||||
const transformed = transformer(response as Awaited<ReturnType<A>>);
|
||||
|
||||
data.value = transformed.data;
|
||||
|
||||
setEmpty(transformed.data.length === 0);
|
||||
|
||||
await config.onFetched?.(transformed);
|
||||
|
||||
endLoading();
|
||||
}
|
||||
|
||||
function formatSearchParams(params: Record<string, unknown>) {
|
||||
const formattedParams: Record<string, unknown> = {};
|
||||
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value !== null && value !== undefined) {
|
||||
formattedParams[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
return formattedParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* update search params
|
||||
*
|
||||
* @param params
|
||||
*/
|
||||
function updateSearchParams(params: Partial<Parameters<A>[0]>) {
|
||||
Object.assign(searchParams, params);
|
||||
}
|
||||
|
||||
/** reset search params */
|
||||
function resetSearchParams() {
|
||||
Object.assign(searchParams, apiParams);
|
||||
}
|
||||
|
||||
if (immediate) {
|
||||
getData();
|
||||
}
|
||||
|
||||
return {
|
||||
loading,
|
||||
empty,
|
||||
data,
|
||||
columns,
|
||||
columnChecks,
|
||||
reloadColumns,
|
||||
getData,
|
||||
searchParams,
|
||||
updateSearchParams,
|
||||
resetSearchParams
|
||||
};
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"jsx": "preserve",
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"noUnusedLocals": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "@sa/materials",
|
||||
"version": "1.1.1",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@sa/utils": "workspace:*",
|
||||
"simplebar-vue": "2.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typed-css-modules": "0.9.1"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import AdminLayout, { LAYOUT_MAX_Z_INDEX, LAYOUT_SCROLL_EL_ID } from './libs/admin-layout';
|
||||
import PageTab from './libs/page-tab';
|
||||
import SimpleScrollbar from './libs/simple-scrollbar';
|
||||
|
||||
export { AdminLayout, LAYOUT_SCROLL_EL_ID, LAYOUT_MAX_Z_INDEX, PageTab, SimpleScrollbar };
|
||||
export * from './types';
|
|
@ -0,0 +1,63 @@
|
|||
/* @type */
|
||||
|
||||
.layout-header,
|
||||
.layout-header-placement {
|
||||
height: var(--soy-header-height);
|
||||
}
|
||||
|
||||
.layout-header {
|
||||
z-index: var(--soy-header-z-index);
|
||||
}
|
||||
|
||||
.layout-tab {
|
||||
top: var(--soy-header-height);
|
||||
height: var(--soy-tab-height);
|
||||
z-index: var(--soy-tab-z-index);
|
||||
}
|
||||
|
||||
.layout-tab-placement {
|
||||
height: var(--soy-tab-height);
|
||||
}
|
||||
|
||||
.layout-sider {
|
||||
width: var(--soy-sider-width);
|
||||
z-index: var(--soy-sider-z-index);
|
||||
}
|
||||
|
||||
.layout-mobile-sider {
|
||||
z-index: var(--soy-sider-z-index);
|
||||
}
|
||||
|
||||
.layout-mobile-sider-mask {
|
||||
z-index: var(--soy-mobile-sider-z-index);
|
||||
}
|
||||
|
||||
.layout-sider_collapsed {
|
||||
width: var(--soy-sider-collapsed-width);
|
||||
z-index: var(--soy-sider-z-index);
|
||||
}
|
||||
|
||||
.layout-footer,
|
||||
.layout-footer-placement {
|
||||
height: var(--soy-footer-height);
|
||||
}
|
||||
|
||||
.layout-footer {
|
||||
z-index: var(--soy-footer-z-index);
|
||||
}
|
||||
|
||||
.left-gap {
|
||||
padding-left: var(--soy-sider-width);
|
||||
}
|
||||
|
||||
.left-gap_collapsed {
|
||||
padding-left: var(--soy-sider-collapsed-width);
|
||||
}
|
||||
|
||||
.sider-padding-top {
|
||||
padding-top: var(--soy-header-height);
|
||||
}
|
||||
|
||||
.sider-padding-bottom {
|
||||
padding-bottom: var(--soy-footer-height);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
declare const styles: {
|
||||
readonly 'layout-header': string;
|
||||
readonly 'layout-header-placement': string;
|
||||
readonly 'layout-tab': string;
|
||||
readonly 'layout-tab-placement': string;
|
||||
readonly 'layout-sider': string;
|
||||
readonly 'layout-mobile-sider': string;
|
||||
readonly 'layout-mobile-sider-mask': string;
|
||||
readonly 'layout-sider_collapsed': string;
|
||||
readonly 'layout-footer': string;
|
||||
readonly 'layout-footer-placement': string;
|
||||
readonly 'left-gap': string;
|
||||
readonly 'left-gap_collapsed': string;
|
||||
readonly 'sider-padding-top': string;
|
||||
readonly 'sider-padding-bottom': string;
|
||||
};
|
||||
|
||||
export default styles;
|
|
@ -0,0 +1,5 @@
|
|||
import AdminLayout from './index.vue';
|
||||
import { LAYOUT_MAX_Z_INDEX, LAYOUT_SCROLL_EL_ID } from './shared';
|
||||
|
||||
export default AdminLayout;
|
||||
export { LAYOUT_SCROLL_EL_ID, LAYOUT_MAX_Z_INDEX };
|
|
@ -0,0 +1,237 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { AdminLayoutProps } from '../../types';
|
||||
import { LAYOUT_MAX_Z_INDEX, LAYOUT_SCROLL_EL_ID, createLayoutCssVars } from './shared';
|
||||
import style from './index.module.css';
|
||||
|
||||
defineOptions({
|
||||
name: 'AdminLayout'
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<AdminLayoutProps>(), {
|
||||
mode: 'vertical',
|
||||
scrollMode: 'content',
|
||||
scrollElId: LAYOUT_SCROLL_EL_ID,
|
||||
commonClass: 'transition-all-300',
|
||||
fixedTop: true,
|
||||
maxZIndex: LAYOUT_MAX_Z_INDEX,
|
||||
headerVisible: true,
|
||||
headerHeight: 56,
|
||||
tabVisible: true,
|
||||
tabHeight: 48,
|
||||
siderVisible: true,
|
||||
siderCollapse: false,
|
||||
siderWidth: 220,
|
||||
siderCollapsedWidth: 64,
|
||||
footerVisible: true,
|
||||
footerHeight: 48,
|
||||
rightFooter: false
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
/** Update siderCollapse */
|
||||
(e: 'update:siderCollapse', collapse: boolean): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
type SlotFn = (props?: Record<string, unknown>) => any;
|
||||
|
||||
type Slots = {
|
||||
/** Main */
|
||||
default?: SlotFn;
|
||||
/** Header */
|
||||
header?: SlotFn;
|
||||
/** Tab */
|
||||
tab?: SlotFn;
|
||||
/** Sider */
|
||||
sider?: SlotFn;
|
||||
/** Footer */
|
||||
footer?: SlotFn;
|
||||
};
|
||||
|
||||
const slots = defineSlots<Slots>();
|
||||
|
||||
const cssVars = computed(() => createLayoutCssVars(props));
|
||||
|
||||
// config visible
|
||||
const showHeader = computed(() => Boolean(slots.header) && props.headerVisible);
|
||||
const showTab = computed(() => Boolean(slots.tab) && props.tabVisible);
|
||||
const showSider = computed(() => !props.isMobile && Boolean(slots.sider) && props.siderVisible);
|
||||
const showMobileSider = computed(() => props.isMobile && Boolean(slots.sider) && props.siderVisible);
|
||||
const showFooter = computed(() => Boolean(slots.footer) && props.footerVisible);
|
||||
|
||||
// scroll mode
|
||||
const isWrapperScroll = computed(() => props.scrollMode === 'wrapper');
|
||||
const isContentScroll = computed(() => props.scrollMode === 'content');
|
||||
|
||||
// layout direction
|
||||
const isVertical = computed(() => props.mode === 'vertical');
|
||||
const isHorizontal = computed(() => props.mode === 'horizontal');
|
||||
|
||||
const fixedHeaderAndTab = computed(() => props.fixedTop || (isHorizontal.value && isWrapperScroll.value));
|
||||
|
||||
// css
|
||||
const leftGapClass = computed(() => {
|
||||
if (!props.fullContent && showSider.value) {
|
||||
return props.siderCollapse ? style['left-gap_collapsed'] : style['left-gap'];
|
||||
}
|
||||
|
||||
return '';
|
||||
});
|
||||
|
||||
const headerLeftGapClass = computed(() => (isVertical.value ? leftGapClass.value : ''));
|
||||
|
||||
const footerLeftGapClass = computed(() => {
|
||||
const condition1 = isVertical.value;
|
||||
const condition2 = isHorizontal.value && isWrapperScroll.value && !props.fixedFooter;
|
||||
const condition3 = Boolean(isHorizontal.value && props.rightFooter);
|
||||
|
||||
if (condition1 || condition2 || condition3) {
|
||||
return leftGapClass.value;
|
||||
}
|
||||
|
||||
return '';
|
||||
});
|
||||
|
||||
const siderPaddingClass = computed(() => {
|
||||
let cls = '';
|
||||
|
||||
if (showHeader.value && !headerLeftGapClass.value) {
|
||||
cls += style['sider-padding-top'];
|
||||
}
|
||||
if (showFooter.value && !footerLeftGapClass.value) {
|
||||
cls += ` ${style['sider-padding-bottom']}`;
|
||||
}
|
||||
|
||||
return cls;
|
||||
});
|
||||
|
||||
function handleClickMask() {
|
||||
emit('update:siderCollapse', true);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative h-full" :class="[commonClass]" :style="cssVars">
|
||||
<div
|
||||
:id="isWrapperScroll ? scrollElId : undefined"
|
||||
class="h-full flex flex-col"
|
||||
:class="[commonClass, scrollWrapperClass, { 'overflow-y-auto': isWrapperScroll }]"
|
||||
>
|
||||
<!-- Header -->
|
||||
<template v-if="showHeader">
|
||||
<header
|
||||
v-show="!fullContent"
|
||||
class="flex-shrink-0"
|
||||
:class="[
|
||||
style['layout-header'],
|
||||
commonClass,
|
||||
headerClass,
|
||||
headerLeftGapClass,
|
||||
{ 'absolute top-0 left-0 w-full': fixedHeaderAndTab }
|
||||
]"
|
||||
>
|
||||
<slot name="header"></slot>
|
||||
</header>
|
||||
<div
|
||||
v-show="!fullContent && fixedHeaderAndTab"
|
||||
class="flex-shrink-0 overflow-hidden"
|
||||
:class="[style['layout-header-placement']]"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<!-- Tab -->
|
||||
<template v-if="showTab">
|
||||
<div
|
||||
class="flex-shrink-0"
|
||||
:class="[
|
||||
style['layout-tab'],
|
||||
commonClass,
|
||||
tabClass,
|
||||
{ 'top-0!': fullContent || !showHeader },
|
||||
leftGapClass,
|
||||
{ 'absolute left-0 w-full': fixedHeaderAndTab }
|
||||
]"
|
||||
>
|
||||
<slot name="tab"></slot>
|
||||
</div>
|
||||
<div
|
||||
v-show="fullContent || fixedHeaderAndTab"
|
||||
class="flex-shrink-0 overflow-hidden"
|
||||
:class="[style['layout-tab-placement']]"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<!-- Sider -->
|
||||
<template v-if="showSider">
|
||||
<aside
|
||||
v-show="!fullContent"
|
||||
class="absolute left-0 top-0 h-full"
|
||||
:class="[
|
||||
commonClass,
|
||||
siderClass,
|
||||
siderPaddingClass,
|
||||
siderCollapse ? style['layout-sider_collapsed'] : style['layout-sider']
|
||||
]"
|
||||
>
|
||||
<slot name="sider"></slot>
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<!-- Mobile Sider -->
|
||||
<template v-if="showMobileSider">
|
||||
<aside
|
||||
class="absolute left-0 top-0 h-full w-0 bg-white"
|
||||
:class="[
|
||||
commonClass,
|
||||
mobileSiderClass,
|
||||
style['layout-mobile-sider'],
|
||||
siderCollapse ? 'overflow-hidden' : style['layout-sider']
|
||||
]"
|
||||
>
|
||||
<slot name="sider"></slot>
|
||||
</aside>
|
||||
<div
|
||||
v-show="!siderCollapse"
|
||||
class="absolute left-0 top-0 h-full w-full bg-[rgba(0,0,0,0.2)]"
|
||||
:class="[style['layout-mobile-sider-mask']]"
|
||||
@click="handleClickMask"
|
||||
></div>
|
||||
</template>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main
|
||||
:id="isContentScroll ? scrollElId : undefined"
|
||||
class="flex flex-col flex-grow"
|
||||
:class="[commonClass, contentClass, leftGapClass, { 'overflow-y-auto': isContentScroll }]"
|
||||
>
|
||||
<slot></slot>
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<template v-if="showFooter">
|
||||
<footer
|
||||
v-show="!fullContent"
|
||||
class="flex-shrink-0"
|
||||
:class="[
|
||||
style['layout-footer'],
|
||||
commonClass,
|
||||
footerClass,
|
||||
footerLeftGapClass,
|
||||
{ 'absolute left-0 bottom-0 w-full': fixedFooter }
|
||||
]"
|
||||
>
|
||||
<slot name="footer"></slot>
|
||||
</footer>
|
||||
<div
|
||||
v-show="!fullContent && fixedFooter"
|
||||
class="flex-shrink-0 overflow-hidden"
|
||||
:class="[style['layout-footer-placement']]"
|
||||
></div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,68 @@
|
|||
import type { AdminLayoutProps, LayoutCssVars, LayoutCssVarsProps } from '../../types';
|
||||
|
||||
/** The id of the scroll element of the layout */
|
||||
export const LAYOUT_SCROLL_EL_ID = '__SCROLL_EL_ID__';
|
||||
|
||||
/** The max z-index of the layout */
|
||||
export const LAYOUT_MAX_Z_INDEX = 100;
|
||||
|
||||
/**
|
||||
* Create layout css vars by css vars props
|
||||
*
|
||||
* @param props Css vars props
|
||||
*/
|
||||
function createLayoutCssVarsByCssVarsProps(props: LayoutCssVarsProps) {
|
||||
const cssVars: LayoutCssVars = {
|
||||
'--soy-header-height': `${props.headerHeight}px`,
|
||||
'--soy-header-z-index': props.headerZIndex,
|
||||
'--soy-tab-height': `${props.tabHeight}px`,
|
||||
'--soy-tab-z-index': props.tabZIndex,
|
||||
'--soy-sider-width': `${props.siderWidth}px`,
|
||||
'--soy-sider-collapsed-width': `${props.siderCollapsedWidth}px`,
|
||||
'--soy-sider-z-index': props.siderZIndex,
|
||||
'--soy-mobile-sider-z-index': props.mobileSiderZIndex,
|
||||
'--soy-footer-height': `${props.footerHeight}px`,
|
||||
'--soy-footer-z-index': props.footerZIndex
|
||||
};
|
||||
|
||||
return cssVars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create layout css vars
|
||||
*
|
||||
* @param props
|
||||
*/
|
||||
export function createLayoutCssVars(props: AdminLayoutProps) {
|
||||
const {
|
||||
mode,
|
||||
isMobile,
|
||||
maxZIndex = LAYOUT_MAX_Z_INDEX,
|
||||
headerHeight,
|
||||
tabHeight,
|
||||
siderWidth,
|
||||
siderCollapsedWidth,
|
||||
footerHeight
|
||||
} = props;
|
||||
|
||||
const headerZIndex = maxZIndex - 3;
|
||||
const tabZIndex = maxZIndex - 5;
|
||||
const siderZIndex = mode === 'vertical' || isMobile ? maxZIndex - 1 : maxZIndex - 4;
|
||||
const mobileSiderZIndex = isMobile ? maxZIndex - 2 : 0;
|
||||
const footerZIndex = maxZIndex - 5;
|
||||
|
||||
const cssProps: LayoutCssVarsProps = {
|
||||
headerHeight,
|
||||
headerZIndex,
|
||||
tabHeight,
|
||||
tabZIndex,
|
||||
siderWidth,
|
||||
siderZIndex,
|
||||
mobileSiderZIndex,
|
||||
siderCollapsedWidth,
|
||||
footerHeight,
|
||||
footerZIndex
|
||||
};
|
||||
|
||||
return createLayoutCssVarsByCssVarsProps(cssProps);
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<script setup lang="ts">
|
||||
import type { PageTabProps } from '../../types';
|
||||
import style from './index.module.css';
|
||||
|
||||
defineOptions({
|
||||
name: 'ButtonTab'
|
||||
});
|
||||
|
||||
defineProps<PageTabProps>();
|
||||
|
||||
type SlotFn = (props?: Record<string, unknown>) => any;
|
||||
|
||||
type Slots = {
|
||||
/**
|
||||
* Slot
|
||||
*
|
||||
* The center content of the tab
|
||||
*/
|
||||
default?: SlotFn;
|
||||
/**
|
||||
* Slot
|
||||
*
|
||||
* The left content of the tab
|
||||
*/
|
||||
prefix?: SlotFn;
|
||||
/**
|
||||
* Slot
|
||||
*
|
||||
* The right content of the tab
|
||||
*/
|
||||
suffix?: SlotFn;
|
||||
};
|
||||
|
||||
defineSlots<Slots>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class=":soy: relative inline-flex cursor-pointer items-center justify-center gap-12px whitespace-nowrap border-1px rounded-4px border-solid px-12px py-4px"
|
||||
:class="[
|
||||
style['button-tab'],
|
||||
{ [style['button-tab_dark']]: darkMode },
|
||||
{ [style['button-tab_active']]: active },
|
||||
{ [style['button-tab_active_dark']]: active && darkMode }
|
||||
]"
|
||||
>
|
||||
<slot name="prefix"></slot>
|
||||
<slot></slot>
|
||||
<slot name="suffix"></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,31 @@
|
|||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'ChromeTabBg'
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<svg class="size-full">
|
||||
<defs>
|
||||
<symbol id="geometry-left" viewBox="0 0 214 36">
|
||||
<path d="M17 0h197v36H0v-2c4.5 0 9-3.5 9-8V8c0-4.5 3.5-8 8-8z" />
|
||||
</symbol>
|
||||
<symbol id="geometry-right" viewBox="0 0 214 36">
|
||||
<use xlink:href="#geometry-left" />
|
||||
</symbol>
|
||||
<clipPath>
|
||||
<rect width="100%" height="100%" x="0" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
<svg width="51%" height="100%">
|
||||
<use xlink:href="#geometry-left" width="214" height="36" fill="currentColor" />
|
||||
</svg>
|
||||
<g transform="scale(-1, 1)">
|
||||
<svg width="51%" height="100%" x="-100%" y="0">
|
||||
<use xlink:href="#geometry-right" width="214" height="36" fill="currentColor" />
|
||||
</svg>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,58 @@
|
|||
<script setup lang="ts">
|
||||
import type { PageTabProps } from '../../types';
|
||||
import ChromeTabBg from './chrome-tab-bg.vue';
|
||||
import style from './index.module.css';
|
||||
|
||||
defineOptions({
|
||||
name: 'ChromeTab'
|
||||
});
|
||||
|
||||
defineProps<PageTabProps>();
|
||||
|
||||
type SlotFn = (props?: Record<string, unknown>) => any;
|
||||
|
||||
type Slots = {
|
||||
/**
|
||||
* Slot
|
||||
*
|
||||
* The center content of the tab
|
||||
*/
|
||||
default?: SlotFn;
|
||||
/**
|
||||
* Slot
|
||||
*
|
||||
* The left content of the tab
|
||||
*/
|
||||
prefix?: SlotFn;
|
||||
/**
|
||||
* Slot
|
||||
*
|
||||
* The right content of the tab
|
||||
*/
|
||||
suffix?: SlotFn;
|
||||
};
|
||||
|
||||
defineSlots<Slots>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class=":soy: relative inline-flex cursor-pointer items-center justify-center gap-16px whitespace-nowrap px-24px py-6px -mr-18px"
|
||||
:class="[
|
||||
style['chrome-tab'],
|
||||
{ [style['chrome-tab_dark']]: darkMode },
|
||||
{ [style['chrome-tab_active']]: active },
|
||||
{ [style['chrome-tab_active_dark']]: active && darkMode }
|
||||
]"
|
||||
>
|
||||
<div class=":soy: pointer-events-none absolute left-0 top-0 h-full w-full -z-1" :class="[style['chrome-tab__bg']]">
|
||||
<ChromeTabBg />
|
||||
</div>
|
||||
<slot name="prefix"></slot>
|
||||
<slot></slot>
|
||||
<slot name="suffix"></slot>
|
||||
<div class=":soy: absolute right-7px h-16px w-1px bg-#1f2225" :class="[style['chrome-tab-divider']]"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,97 @@
|
|||
/* @type */
|
||||
|
||||
.button-tab {
|
||||
border-color: #e5e7eb;
|
||||
}
|
||||
|
||||
.button-tab_dark {
|
||||
border-color: #ffffff3d;
|
||||
}
|
||||
|
||||
.button-tab:hover {
|
||||
color: var(--soy-primary-color);
|
||||
border-color: var(--soy-primary-color-opacity3);
|
||||
}
|
||||
|
||||
.button-tab_active {
|
||||
color: var(--soy-primary-color);
|
||||
border-color: var(--soy-primary-color-opacity3);
|
||||
background-color: var(--soy-primary-color-opacity1);
|
||||
}
|
||||
|
||||
.button-tab_active_dark {
|
||||
background-color: var(--soy-primary-color-opacity2);
|
||||
}
|
||||
|
||||
.button-tab .svg-close:hover {
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
background-color: var(--soy-primary-color);
|
||||
}
|
||||
|
||||
.button-tab_dark .svg-close:hover {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.chrome-tab:hover {
|
||||
z-index: 9;
|
||||
}
|
||||
|
||||
.chrome-tab_active {
|
||||
z-index: 10;
|
||||
color: var(--soy-primary-color);
|
||||
}
|
||||
|
||||
.chrome-tab__bg {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.chrome-tab_active .chrome-tab__bg {
|
||||
color: var(--soy-primary-color1);
|
||||
}
|
||||
|
||||
.chrome-tab_active_dark .chrome-tab__bg {
|
||||
color: var(--soy-primary-color2);
|
||||
}
|
||||
|
||||
.chrome-tab:hover .chrome-tab__bg {
|
||||
color: #dee1e6;
|
||||
}
|
||||
|
||||
.chrome-tab_active:hover .chrome-tab__bg {
|
||||
color: var(--soy-primary-color1);
|
||||
}
|
||||
|
||||
.chrome-tab_dark:hover .chrome-tab__bg {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.chrome-tab_active_dark:hover .chrome-tab__bg {
|
||||
color: var(--soy-primary-color2);
|
||||
}
|
||||
|
||||
.chrome-tab .svg-close:hover {
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
background-color: #9ca3af;
|
||||
}
|
||||
|
||||
.chrome-tab_active .svg-close:hover {
|
||||
background-color: var(--soy-primary-color);
|
||||
}
|
||||
|
||||
.chrome-tab_dark .svg-close:hover {
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.chrome-tab_active .chrome-tab-divider {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.chrome-tab:hover .chrome-tab-divider {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.chrome-tab_dark .chrome-tab-divider {
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
declare const styles: {
|
||||
readonly 'button-tab': string;
|
||||
readonly 'button-tab_dark': string;
|
||||
readonly 'button-tab_active': string;
|
||||
readonly 'button-tab_active_dark': string;
|
||||
readonly 'chrome-tab': string;
|
||||
readonly 'chrome-tab_active': string;
|
||||
readonly 'chrome-tab__bg': string;
|
||||
readonly 'chrome-tab_active_dark': string;
|
||||
readonly 'chrome-tab_dark': string;
|
||||
readonly 'chrome-tab-divider': string;
|
||||
readonly 'svg-close': string;
|
||||
};
|
||||
|
||||
export default styles;
|
|
@ -0,0 +1,3 @@
|
|||
import PageTab from './index.vue';
|
||||
|
||||
export default PageTab;
|
|
@ -0,0 +1,85 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { Component } from 'vue';
|
||||
import type { PageTabMode, PageTabProps } from '../../types';
|
||||
import { ACTIVE_COLOR, createTabCssVars } from './shared';
|
||||
import ChromeTab from './chrome-tab.vue';
|
||||
import ButtonTab from './button-tab.vue';
|
||||
import SvgClose from './svg-close.vue';
|
||||
import style from './index.module.css';
|
||||
|
||||
defineOptions({
|
||||
name: 'PageTab'
|
||||
});
|
||||
|
||||
const props = withDefaults(defineProps<PageTabProps>(), {
|
||||
mode: 'chrome',
|
||||
commonClass: 'transition-all-300',
|
||||
activeColor: ACTIVE_COLOR,
|
||||
closable: true
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
(e: 'close'): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const activeTabComponent = computed(() => {
|
||||
const { mode, chromeClass, buttonClass } = props;
|
||||
|
||||
const tabComponentMap = {
|
||||
chrome: {
|
||||
component: ChromeTab,
|
||||
class: chromeClass
|
||||
},
|
||||
button: {
|
||||
component: ButtonTab,
|
||||
class: buttonClass
|
||||
}
|
||||
} satisfies Record<PageTabMode, { component: Component; class?: string }>;
|
||||
|
||||
return tabComponentMap[mode];
|
||||
});
|
||||
|
||||
const cssVars = computed(() => createTabCssVars(props.activeColor));
|
||||
|
||||
const bindProps = computed(() => {
|
||||
const { chromeClass: _chromeCls, buttonClass: _btnCls, ...rest } = props;
|
||||
|
||||
return rest;
|
||||
});
|
||||
|
||||
function handleClose() {
|
||||
emit('close');
|
||||
}
|
||||
|
||||
function handleMouseup(e: MouseEvent) {
|
||||
// close tab by mouse wheel button click
|
||||
if (e.button === 1) {
|
||||
handleClose();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<component
|
||||
:is="activeTabComponent.component"
|
||||
:class="activeTabComponent.class"
|
||||
:style="cssVars"
|
||||
v-bind="bindProps"
|
||||
@mouseup="handleMouseup"
|
||||
>
|
||||
<template #prefix>
|
||||
<slot name="prefix"></slot>
|
||||
</template>
|
||||
<slot></slot>
|
||||
<template #suffix>
|
||||
<slot name="suffix">
|
||||
<SvgClose v-if="closable" :class="[style['svg-close']]" @click.stop="handleClose" />
|
||||
</slot>
|
||||
</template>
|
||||
</component>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,31 @@
|
|||
import { addColorAlpha, transformColorWithOpacity } from '@sa/color';
|
||||
import type { PageTabCssVars, PageTabCssVarsProps } from '../../types';
|
||||
|
||||
/** The active color of the tab */
|
||||
export const ACTIVE_COLOR = '#1890ff';
|
||||
|
||||
function createCssVars(props: PageTabCssVarsProps) {
|
||||
const cssVars: PageTabCssVars = {
|
||||
'--soy-primary-color': props.primaryColor,
|
||||
'--soy-primary-color1': props.primaryColor1,
|
||||
'--soy-primary-color2': props.primaryColor2,
|
||||
'--soy-primary-color-opacity1': props.primaryColorOpacity1,
|
||||
'--soy-primary-color-opacity2': props.primaryColorOpacity2,
|
||||
'--soy-primary-color-opacity3': props.primaryColorOpacity3
|
||||
};
|
||||
|
||||
return cssVars;
|
||||
}
|
||||
|
||||
export function createTabCssVars(primaryColor: string) {
|
||||
const cssProps: PageTabCssVarsProps = {
|
||||
primaryColor,
|
||||
primaryColor1: transformColorWithOpacity(primaryColor, 0.1, '#ffffff'),
|
||||
primaryColor2: transformColorWithOpacity(primaryColor, 0.3, '#000000'),
|
||||
primaryColorOpacity1: addColorAlpha(primaryColor, 0.1),
|
||||
primaryColorOpacity2: addColorAlpha(primaryColor, 0.15),
|
||||
primaryColorOpacity3: addColorAlpha(primaryColor, 0.3)
|
||||
};
|
||||
|
||||
return createCssVars(cssProps);
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<script setup lang="ts">
|
||||
defineOptions({
|
||||
name: 'SvgClose'
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class=":soy: relative h-16px w-16px inline-flex items-center justify-center rd-50% text-14px">
|
||||
<svg width="1em" height="1em" viewBox="0 0 1024 1024">
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="m563.8 512l262.5-312.9c4.4-5.2.7-13.1-6.1-13.1h-79.8c-4.7 0-9.2 2.1-12.3 5.7L511.6 449.8L295.1 191.7c-3-3.6-7.5-5.7-12.3-5.7H203c-6.8 0-10.5 7.9-6.1 13.1L459.4 512L196.9 824.9A7.95 7.95 0 0 0 203 838h79.8c4.7 0 9.2-2.1 12.3-5.7l216.5-258.1l216.5 258.1c3 3.6 7.5 5.7 12.3 5.7h79.8c6.8 0 10.5-7.9 6.1-13.1L563.8 512z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,3 @@
|
|||
import SimpleScrollbar from './index.vue';
|
||||
|
||||
export default SimpleScrollbar;
|
|
@ -0,0 +1,18 @@
|
|||
<script setup lang="ts">
|
||||
import Simplebar from 'simplebar-vue';
|
||||
import 'simplebar-vue/dist/simplebar.min.css';
|
||||
|
||||
defineOptions({
|
||||
name: 'SimpleScrollbar'
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-full flex-1-hidden">
|
||||
<Simplebar class="h-full">
|
||||
<slot />
|
||||
</Simplebar>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
|
@ -0,0 +1,294 @@
|
|||
/** Header config */
|
||||
interface AdminLayoutHeaderConfig {
|
||||
/**
|
||||
* Whether header is visible
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
headerVisible?: boolean;
|
||||
/**
|
||||
* Header class
|
||||
*
|
||||
* @default ''
|
||||
*/
|
||||
headerClass?: string;
|
||||
/**
|
||||
* Header height
|
||||
*
|
||||
* @default 56px
|
||||
*/
|
||||
headerHeight?: number;
|
||||
}
|
||||
|
||||
/** Tab config */
|
||||
interface AdminLayoutTabConfig {
|
||||
/**
|
||||
* Whether tab is visible
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
tabVisible?: boolean;
|
||||
/**
|
||||
* Tab class
|
||||
*
|
||||
* @default ''
|
||||
*/
|
||||
tabClass?: string;
|
||||
/**
|
||||
* Tab height
|
||||
*
|
||||
* @default 48px
|
||||
*/
|
||||
tabHeight?: number;
|
||||
}
|
||||
|
||||
/** Sider config */
|
||||
interface AdminLayoutSiderConfig {
|
||||
/**
|
||||
* Whether sider is visible
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
siderVisible?: boolean;
|
||||
/**
|
||||
* Sider class
|
||||
*
|
||||
* @default ''
|
||||
*/
|
||||
siderClass?: string;
|
||||
/**
|
||||
* Mobile sider class
|
||||
*
|
||||
* @default ''
|
||||
*/
|
||||
mobileSiderClass?: string;
|
||||
/**
|
||||
* Sider collapse status
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
siderCollapse?: boolean;
|
||||
/**
|
||||
* Sider width when collapse is false
|
||||
*
|
||||
* @default '220px'
|
||||
*/
|
||||
siderWidth?: number;
|
||||
/**
|
||||
* Sider width when collapse is true
|
||||
*
|
||||
* @default '64px'
|
||||
*/
|
||||
siderCollapsedWidth?: number;
|
||||
}
|
||||
|
||||
/** Content config */
|
||||
export interface AdminLayoutContentConfig {
|
||||
/**
|
||||
* Content class
|
||||
*
|
||||
* @default ''
|
||||
*/
|
||||
contentClass?: string;
|
||||
/**
|
||||
* Whether content is full the page
|
||||
*
|
||||
* If true, other elements will be hidden by `display: none`
|
||||
*/
|
||||
fullContent?: boolean;
|
||||
}
|
||||
|
||||
/** Footer config */
|
||||
export interface AdminLayoutFooterConfig {
|
||||
/**
|
||||
* Whether footer is visible
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
footerVisible?: boolean;
|
||||
/**
|
||||
* Whether footer is fixed
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
fixedFooter?: boolean;
|
||||
/**
|
||||
* Footer class
|
||||
*
|
||||
* @default ''
|
||||
*/
|
||||
footerClass?: string;
|
||||
/**
|
||||
* Footer height
|
||||
*
|
||||
* @default 48px
|
||||
*/
|
||||
footerHeight?: number;
|
||||
/**
|
||||
* Whether footer is on the right side
|
||||
*
|
||||
* When the layout is vertical, the footer is on the right side
|
||||
*/
|
||||
rightFooter?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout mode
|
||||
*
|
||||
* - Horizontal
|
||||
* - Vertical
|
||||
*/
|
||||
export type LayoutMode = 'horizontal' | 'vertical';
|
||||
|
||||
/**
|
||||
* The scroll mode when content overflow
|
||||
*
|
||||
* - Wrapper: the layout component's wrapper element has a scrollbar
|
||||
* - Content: the layout component's content element has a scrollbar
|
||||
*
|
||||
* @default 'wrapper'
|
||||
*/
|
||||
export type LayoutScrollMode = 'wrapper' | 'content';
|
||||
|
||||
/** Admin layout props */
|
||||
export interface AdminLayoutProps
|
||||
extends AdminLayoutHeaderConfig,
|
||||
AdminLayoutTabConfig,
|
||||
AdminLayoutSiderConfig,
|
||||
AdminLayoutContentConfig,
|
||||
AdminLayoutFooterConfig {
|
||||
/**
|
||||
* Layout mode
|
||||
*
|
||||
* - {@link LayoutMode}
|
||||
*/
|
||||
mode?: LayoutMode;
|
||||
/** Is mobile layout */
|
||||
isMobile?: boolean;
|
||||
/**
|
||||
* Scroll mode
|
||||
*
|
||||
* - {@link ScrollMode}
|
||||
*/
|
||||
scrollMode?: LayoutScrollMode;
|
||||
/**
|
||||
* The id of the scroll element of the layout
|
||||
*
|
||||
* It can be used to get the corresponding Dom and scroll it
|
||||
*
|
||||
* @example
|
||||
* use the default id by import
|
||||
* ```ts
|
||||
* import { adminLayoutScrollElId } from '@sa/vue-materials';
|
||||
* ```
|
||||
*
|
||||
* @default
|
||||
* ```ts
|
||||
* const adminLayoutScrollElId = '__ADMIN_LAYOUT_SCROLL_EL_ID__'
|
||||
* ```
|
||||
*/
|
||||
scrollElId?: string;
|
||||
/** The class of the scroll element */
|
||||
scrollElClass?: string;
|
||||
/** The class of the scroll wrapper element */
|
||||
scrollWrapperClass?: string;
|
||||
/**
|
||||
* The common class of the layout
|
||||
*
|
||||
* Is can be used to configure the transition animation
|
||||
*
|
||||
* @default 'transition-all-300'
|
||||
*/
|
||||
commonClass?: string;
|
||||
/**
|
||||
* Whether fix the header and tab
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
fixedTop?: boolean;
|
||||
/**
|
||||
* The max z-index of the layout
|
||||
*
|
||||
* The z-index of Header,Tab,Sider and Footer will not exceed this value
|
||||
*/
|
||||
maxZIndex?: number;
|
||||
}
|
||||
|
||||
type Kebab<S extends string> = S extends Uncapitalize<S> ? S : `-${Uncapitalize<S>}`;
|
||||
|
||||
type KebabCase<S extends string> = S extends `${infer Start}${infer End}`
|
||||
? `${Uncapitalize<Start>}${KebabCase<Kebab<End>>}`
|
||||
: S;
|
||||
|
||||
type Prefix = '--soy-';
|
||||
|
||||
export type LayoutCssVarsProps = Pick<
|
||||
AdminLayoutProps,
|
||||
'headerHeight' | 'tabHeight' | 'siderWidth' | 'siderCollapsedWidth' | 'footerHeight'
|
||||
> & {
|
||||
headerZIndex?: number;
|
||||
tabZIndex?: number;
|
||||
siderZIndex?: number;
|
||||
mobileSiderZIndex?: number;
|
||||
footerZIndex?: number;
|
||||
};
|
||||
|
||||
export type LayoutCssVars = {
|
||||
[K in keyof LayoutCssVarsProps as `${Prefix}${KebabCase<K>}`]: string | number;
|
||||
};
|
||||
|
||||
/**
|
||||
* The mode of the tab
|
||||
*
|
||||
* - Button: button style
|
||||
* - Chrome: chrome style
|
||||
*
|
||||
* @default chrome
|
||||
*/
|
||||
export type PageTabMode = 'button' | 'chrome';
|
||||
|
||||
export interface PageTabProps {
|
||||
/** Whether is dark mode */
|
||||
darkMode?: boolean;
|
||||
/**
|
||||
* The mode of the tab
|
||||
*
|
||||
* - {@link TabMode}
|
||||
*/
|
||||
mode?: PageTabMode;
|
||||
/**
|
||||
* The common class of the layout
|
||||
*
|
||||
* Is can be used to configure the transition animation
|
||||
*
|
||||
* @default 'transition-all-300'
|
||||
*/
|
||||
commonClass?: string;
|
||||
/** The class of the button tab */
|
||||
buttonClass?: string;
|
||||
/** The class of the chrome tab */
|
||||
chromeClass?: string;
|
||||
/** Whether the tab is active */
|
||||
active?: boolean;
|
||||
/** The color of the active tab */
|
||||
activeColor?: string;
|
||||
/**
|
||||
* Whether the tab is closable
|
||||
*
|
||||
* Show the close icon when true
|
||||
*/
|
||||
closable?: boolean;
|
||||
}
|
||||
|
||||
export type PageTabCssVarsProps = {
|
||||
primaryColor: string;
|
||||
primaryColor1: string;
|
||||
primaryColor2: string;
|
||||
primaryColorOpacity1: string;
|
||||
primaryColorOpacity2: string;
|
||||
primaryColorOpacity3: string;
|
||||
};
|
||||
|
||||
export type PageTabCssVars = {
|
||||
[K in keyof PageTabCssVarsProps as `${Prefix}${KebabCase<K>}`]: string | number;
|
||||
};
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"jsx": "preserve",
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"noUnusedLocals": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"name": "@sa/fetch",
|
||||
"version": "1.1.1",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"ofetch": "1.3.4"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import { ofetch } from 'ofetch';
|
||||
import type { FetchOptions } from 'ofetch';
|
||||
|
||||
export function createRequest(options: FetchOptions) {
|
||||
const request = ofetch.create(options);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
export default createRequest;
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"jsx": "preserve",
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"noUnusedLocals": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
// https://eslint.org/docs/user-guide/configuring
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
ecmaVersion: 6,
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
impliedStrict: true,
|
||||
objectLiteralDuplicateProperties: false
|
||||
}
|
||||
},
|
||||
env: {
|
||||
amd: true,
|
||||
browser: true,
|
||||
node: true
|
||||
},
|
||||
plugins: [
|
||||
'@typescript-eslint'
|
||||
],
|
||||
extends: [
|
||||
'prettier/@typescript-eslint',
|
||||
'standard'
|
||||
],
|
||||
rules: {
|
||||
'array-bracket-spacing': ['error', 'never'],
|
||||
'no-debugger': ['error'],
|
||||
'keyword-spacing': ['error']
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
.DS_Store
|
||||
.history
|
||||
node_modules
|
||||
/dist
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2019 Xu Liangzhan
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,151 @@
|
|||
# @vxe-ui/plugin-render-antd
|
||||
|
||||
[Vxe UI](https://vxeui.com/) plug-in for compatibility with the [ant-design-vue](https://www.npmjs.com/package/ant-design-vue) component.
|
||||
|
||||
## Compatibility
|
||||
|
||||
It corresponds to [vxe-table v4](https://www.npmjs.com/package/vxe-table) or [vxe-pc-ui v4](https://www.npmjs.com/package/vxe-pc-ui)
|
||||
|
||||
## Installing
|
||||
|
||||
```shell
|
||||
npm install @vxe-ui/plugin-render-antd
|
||||
```
|
||||
|
||||
```javascript
|
||||
// ...
|
||||
// Use vxe-pc-ui
|
||||
import { VxeUI } from 'vxe-pc-ui'
|
||||
// Use vxe-table
|
||||
// import { VxeUI } from 'vxe-table'
|
||||
import VxeUIPluginRenderNaive from '@vxe-ui/plugin-render-antd'
|
||||
import '@vxe-ui/plugin-render-antd/dist/style.css'
|
||||
// ...
|
||||
|
||||
VxeUI.use(VxeUIPluginRenderNaive)
|
||||
```
|
||||
|
||||
|
||||
## API
|
||||
|
||||
### cell-render 默认的渲染器配置项说明
|
||||
|
||||
| 属性 | 描述 | 类型 | 可选值 | 默认值 |
|
||||
|------|------|-----|-----|-----|
|
||||
| name | 支持的渲染组件 | String | AInput, AAutocomplete, AInputNumber, ASwitch, ARate, AButton, AButtons | — |
|
||||
| props | 渲染组件附加属性,参数请查看被渲染的 Component props | Object | — | {} |
|
||||
| options | 只对 name=ASelect 有效,下拉组件选项列表 | Array | — | [] |
|
||||
| optionProps | 只对 name=ASelect 有效,下拉组件选项属性参数配置 | Object | — | { value: 'value', label: 'label' } |
|
||||
| optionGroups | 只对 name=ASelect 有效,下拉组件分组选项列表 | Array | — | [] |
|
||||
| optionGroupProps | 只对 name=ASelect 有效,下拉组件分组选项属性参数配置 | Object | — | { options: 'options', label: 'label' } |
|
||||
| events | 渲染组件附加事件,参数为 ( {row,rowIndex,column,columnIndex}, ...Component arguments ) | Object | — | — |
|
||||
|
||||
### edit-render 可编辑渲染器配置项说明
|
||||
|
||||
| 属性 | 描述 | 类型 | 可选值 | 默认值 |
|
||||
|------|------|-----|-----|-----|
|
||||
| name | 支持的渲染组件 | String | AInput, AAutocomplete, AInputNumber, ASelect, ACascader, ADatePicker, AMonthPicker, ARangePicker, AWeekPicker, ATimePicker, ATreeSelect, ASwitch, ARate, AButton, AButtons | — |
|
||||
| props | 渲染组件附加属性,参数请查看被渲染的 Component props | Object | — | {} |
|
||||
| options | 只对 name=ASelect 有效,下拉组件选项列表 | Array | — | [] |
|
||||
| optionProps | 只对 name=ASelect 有效,下拉组件选项属性参数配置 | Object | — | { value: 'value', label: 'label' } |
|
||||
| optionGroups | 只对 name=ASelect 有效,下拉组件分组选项列表 | Array | — | [] |
|
||||
| optionGroupProps | 只对 name=ASelect 有效,下拉组件分组选项属性参数配置 | Object | — | { options: 'options', label: 'label' } |
|
||||
| events | 渲染组件附加事件,参数为 ( {row,rowIndex,column,columnIndex}, ...Component arguments ) | Object | — | — |
|
||||
|
||||
### filter-render 筛选渲染器配置项说明
|
||||
|
||||
| 属性 | 描述 | 类型 | 可选值 | 默认值 |
|
||||
|------|------|-----|-----|-----|
|
||||
| name | 支持的渲染组件 | String | AInput, AAutocomplete, AInputNumber, ASelect, ASwitch, ARate | — |
|
||||
| props | 渲染组件附加属性,参数请查看被渲染的 Component props | Object | — | {} |
|
||||
| options | 只对 name=ASelect 有效,下拉组件选项列表 | Array | — | [] |
|
||||
| optionProps | 只对 name=ASelect 有效,下拉组件选项属性参数配置 | Object | — | { value: 'value', label: 'label' } |
|
||||
| optionGroups | 只对 name=ASelect 有效,下拉组件分组选项列表 | Array | — | [] |
|
||||
| optionGroupProps | 只对 name=ASelect 有效,下拉组件分组选项属性参数配置 | Object | — | { options: 'options', label: 'label' } |
|
||||
| events | 渲染组件附加事件,参数为 ( {}, ...Component arguments ) | Object | — | — |
|
||||
|
||||
### item-render 表单-项选渲染器配置项说明
|
||||
|
||||
| 属性 | 描述 | 类型 | 可选值 | 默认值 |
|
||||
|------|------|-----|-----|-----|
|
||||
| name | 支持的渲染组件 | String | AInput, AAutocomplete, AInputNumber, ASelect, ASwitch, ARate, ARadio, ACheckbox, AButton, AButtons | — |
|
||||
| props | 渲染组件附加属性,参数请查看被渲染的 Component props | Object | — | {} |
|
||||
| options | 只对 name=ASelect 有效,下拉组件选项列表 | Array | — | [] |
|
||||
| optionProps | 只对 name=ASelect 有效,下拉组件选项属性参数配置 | Object | — | { value: 'value', label: 'label' } |
|
||||
| optionGroups | 只对 name=ASelect 有效,下拉组件分组选项列表 | Array | — | [] |
|
||||
| optionGroupProps | 只对 name=ASelect 有效,下拉组件分组选项属性参数配置 | Object | — | { options: 'options', label: 'label' } |
|
||||
| events | 渲染组件附加事件,参数为 ( {}, ...Component arguments ) | Object | — | — |
|
||||
|
||||
### 表单设计器配置项
|
||||
|
||||
| 描述 |
|
||||
|------|
|
||||
| 'AInputWidget', 'ATextareaWidget', 'ANumberInputWidget', 'ADatePickerWidget', 'ASelectWidget', 'ARadioWidget', 'ACheckboxWidget', 'ASwitchWidget' |
|
||||
|
||||
## Cell demo
|
||||
|
||||
```html
|
||||
<vxe-table
|
||||
height="600"
|
||||
:data="tableData"
|
||||
:edit-config="{trigger: 'click', mode: 'cell'}">
|
||||
<vxe-column field="name" title="Name" :edit-render="{name: 'AInput'}"></vxe-column>
|
||||
<vxe-column field="age" title="Age" :edit-render="{name: 'AInputNumber'}"></vxe-column>
|
||||
<vxe-column field="date" title="Date" width="200" :edit-render="{name: 'ADatePicker'}"></vxe-column>
|
||||
</vxe-table>
|
||||
```
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
tableData: [
|
||||
{ id: 100, name: 'test0', age: 28, sex: '1', date: '' },
|
||||
{ id: 101, name: 'test1', age: 32, sex: '0', date: '' },
|
||||
{ id: 102, name: 'test2', age: 36, sex: '1', date: '' }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Filter demo
|
||||
|
||||
```html
|
||||
<vxe-table
|
||||
height="600"
|
||||
:data="tableData">
|
||||
<vxe-column field="name" title="Name":filters="nameOptions" :filter-render="{name: 'AInput'}"></vxe-column>
|
||||
<vxe-column field="age" title="Age"></vxe-column>
|
||||
<vxe-column field="date" title="Date" ></vxe-column>
|
||||
</vxe-table>
|
||||
```
|
||||
|
||||
```javascript
|
||||
import { defineComponent } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
setup () {
|
||||
return {
|
||||
nameOptions: [
|
||||
{ data: '' }
|
||||
],
|
||||
tableData: [
|
||||
{ id: 100, name: 'test0', age: 28, date: '' },
|
||||
{ id: 101, name: 'test1', age: 32, date: '' },
|
||||
{ id: 102, name: 'test2', age: 36, date: '' }
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Contributors
|
||||
|
||||
Thank you to everyone who contributed to this project.
|
||||
|
||||
[![vxe-ui-plugins](https://contrib.rocks/image?repo=x-extends/vxe-ui-plugins)](https://github.com/x-extends/vxe-ui-plugins/graphs/contributors)
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE) © 2019-present, Xu Liangzhan
|
|
@ -0,0 +1,125 @@
|
|||
const gulp = require('gulp')
|
||||
const del = require('del')
|
||||
const uglify = require('gulp-uglify')
|
||||
const babel = require('gulp-babel')
|
||||
const rename = require('gulp-rename')
|
||||
const replace = require('gulp-replace')
|
||||
const dartSass = require('sass')
|
||||
const gulpSass = require('gulp-sass')
|
||||
const sass = gulpSass(dartSass)
|
||||
const cleanCSS = require('gulp-clean-css')
|
||||
const prefixer = require('gulp-autoprefixer')
|
||||
const sourcemaps = require('gulp-sourcemaps')
|
||||
const ts = require('gulp-typescript')
|
||||
const browserify = require('browserify')
|
||||
const source = require('vinyl-source-stream')
|
||||
const pack = require('./package.json')
|
||||
const tsconfig = require('./tsconfig.json')
|
||||
|
||||
const exportModuleName = 'VxeUIPluginRenderNaive'
|
||||
|
||||
gulp.task('build_style', function () {
|
||||
return gulp.src('style.scss')
|
||||
.pipe(sass())
|
||||
.pipe(prefixer({
|
||||
borwsers: ['last 1 version', '> 1%', 'not ie <= 8'],
|
||||
cascade: true,
|
||||
remove: true
|
||||
}))
|
||||
.pipe(gulp.dest('dist'))
|
||||
.pipe(cleanCSS())
|
||||
.pipe(rename({
|
||||
extname: '.min.css'
|
||||
}))
|
||||
.pipe(gulp.dest('dist'))
|
||||
})
|
||||
|
||||
gulp.task('build_ts', function () {
|
||||
return gulp.src(['src/**/*.ts'])
|
||||
.pipe(ts(tsconfig.compilerOptions))
|
||||
.pipe(babel({
|
||||
presets: ['@babel/env']
|
||||
}))
|
||||
.pipe(gulp.dest('dist'))
|
||||
})
|
||||
|
||||
gulp.task('build_commonjs', function () {
|
||||
return gulp.src(['dist/index.js'])
|
||||
.pipe(rename({
|
||||
basename: 'index',
|
||||
extname: '.common.js'
|
||||
}))
|
||||
.pipe(gulp.dest('dist'))
|
||||
})
|
||||
|
||||
gulp.task('browserify_common', function () {
|
||||
return browserify({
|
||||
entries: 'dist/index.js'
|
||||
}).external([
|
||||
'vue',
|
||||
'xe-utils',
|
||||
'dayjs'
|
||||
])
|
||||
.bundle()
|
||||
.pipe(source('all.common.js'))
|
||||
.pipe(gulp.dest('dist'))
|
||||
})
|
||||
|
||||
gulp.task('build_umd', gulp.series('browserify_common', function () {
|
||||
return gulp.src(['dist/all.common.js'])
|
||||
.pipe(babel({
|
||||
moduleId: pack.name,
|
||||
presets: [
|
||||
'@babel/env'
|
||||
],
|
||||
plugins: [
|
||||
['@babel/transform-modules-umd', {
|
||||
globals: {
|
||||
[pack.name]: exportModuleName,
|
||||
vue: 'Vue',
|
||||
'xe-utils': 'XEUtils',
|
||||
dayjs: 'dayjs'
|
||||
},
|
||||
exactGlobals: true
|
||||
}]
|
||||
]
|
||||
}))
|
||||
.pipe(rename({
|
||||
basename: 'index',
|
||||
suffix: '.umd',
|
||||
extname: '.js'
|
||||
}))
|
||||
.pipe(gulp.dest('dist'))
|
||||
.pipe(uglify())
|
||||
.pipe(rename({
|
||||
basename: 'index',
|
||||
suffix: '.umd.min',
|
||||
extname: '.js'
|
||||
}))
|
||||
.pipe(gulp.dest('dist'))
|
||||
}))
|
||||
|
||||
gulp.task('build_esm', function () {
|
||||
return gulp.src(['src/**/*.ts'])
|
||||
.pipe(ts({
|
||||
...tsconfig.compilerOptions,
|
||||
module: 'ESNext'
|
||||
}))
|
||||
.pipe(babel({
|
||||
presets: [['@babel/env', { modules: false }]],
|
||||
plugins: ['@babel/plugin-transform-runtime']
|
||||
}))
|
||||
.pipe(rename({
|
||||
basename: 'index',
|
||||
extname: '.esm.js'
|
||||
}))
|
||||
.pipe(gulp.dest('dist'));
|
||||
});
|
||||
|
||||
gulp.task('clear', () => {
|
||||
return del([
|
||||
'dist'
|
||||
])
|
||||
})
|
||||
|
||||
gulp.task('build', gulp.series('clear', 'build_ts', gulp.parallel('build_esm', 'build_style')))
|
|
@ -0,0 +1,73 @@
|
|||
{
|
||||
"name": "@vxe-ui/plugin-render-naive",
|
||||
"version": "4.0.5",
|
||||
"description": "Vxe UI plug-in for compatibility with the ant-design-vue component.",
|
||||
"scripts": {
|
||||
"lib": "gulp build"
|
||||
},
|
||||
"files": [
|
||||
"types",
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"main": "dist/index.common.js",
|
||||
"unpkg": "dist/index.umd.js",
|
||||
"jsdelivr": "dist/index.umd.js",
|
||||
"style": "dist/style.css",
|
||||
"typings": "types/index.d.ts",
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.12.3",
|
||||
"@babel/plugin-transform-runtime": "^7.12.1",
|
||||
"@babel/preset-env": "^7.12.1",
|
||||
"@babel/runtime": "^7.12.5",
|
||||
"@typescript-eslint/eslint-plugin": "^4.6.1",
|
||||
"@typescript-eslint/parser": "^4.6.1",
|
||||
"browserify": "^17.0.0",
|
||||
"del": "^6.0.0",
|
||||
"eslint": "^7.13.0",
|
||||
"eslint-config-prettier": "^6.15.0",
|
||||
"eslint-config-standard": "^16.0.1",
|
||||
"eslint-friendly-formatter": "^4.0.1",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-prettier": "^3.1.4",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-standard": "^4.0.2",
|
||||
"eslint-plugin-typescript": "^0.14.0",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-autoprefixer": "^8.0.0",
|
||||
"gulp-babel": "^8.0.0",
|
||||
"gulp-clean-css": "^4.3.0",
|
||||
"gulp-concat": "^2.6.1",
|
||||
"gulp-rename": "^2.0.0",
|
||||
"gulp-replace": "^1.1.3",
|
||||
"gulp-sass": "^5.1.0",
|
||||
"gulp-sourcemaps": "^3.0.0",
|
||||
"gulp-typescript": "^5.0.1",
|
||||
"gulp-uglify": "^3.0.2",
|
||||
"markdown-doctest": "^1.1.0",
|
||||
"prettier": "^2.1.2",
|
||||
"sass": "^1.55.0",
|
||||
"typescript": "^4.6.4",
|
||||
"vinyl-source-stream": "^2.0.0",
|
||||
"vue": "^3.4.27",
|
||||
"vxe-pc-ui": "^4.0.35",
|
||||
"vxe-table": "^4.7.36"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/x-extends/vxe-ui-plugins.git"
|
||||
},
|
||||
"keywords": [
|
||||
"vxe-ui-plugins"
|
||||
],
|
||||
"author": {
|
||||
"name": "Xu Liangzhan",
|
||||
"email": "xu_liangzhan@163.com"
|
||||
},
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/x-extends/vxe-ui-plugins/issues"
|
||||
},
|
||||
"homepage": "https://github.com/x-extends/vxe-ui-plugins#readme"
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
import { defineComponent, h, PropType, resolveComponent, ComponentOptions, computed } from 'vue'
|
||||
import { useWidgetPropDataSource, WidgetDataSourceOptionObjVO } from './use'
|
||||
import XEUtils from 'xe-utils'
|
||||
|
||||
import type { VxeUIExport, VxeGlobalRendererHandles, VxeFormComponent, VxeFormItemComponent, VxeSwitchComponent, VxeInputComponent } from 'vxe-pc-ui'
|
||||
|
||||
interface WidgetACheckboxFormObjVO {
|
||||
options: WidgetDataSourceOptionObjVO[]
|
||||
}
|
||||
|
||||
export function createWidgetACheckbox (VxeUI: VxeUIExport) {
|
||||
const getWidgetACheckboxConfig = (params: VxeGlobalRendererHandles.CreateFormDesignWidgetConfigParams): VxeGlobalRendererHandles.CreateFormDesignWidgetConfigObj<WidgetACheckboxFormObjVO> => {
|
||||
return {
|
||||
title: '复选框',
|
||||
icon: 'vxe-icon-checkbox-checked',
|
||||
options: {
|
||||
options: XEUtils.range(0, 3).map((v, i) => {
|
||||
return {
|
||||
value: VxeUI.getI18n('vxe.formDesign.widgetProp.dataSource.defValue', [i + 1])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const WidgetACheckboxFormComponent = defineComponent({
|
||||
props: {
|
||||
renderOpts: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetFormViewOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
renderParams: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetFormViewParams<WidgetACheckboxFormObjVO>>,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: [],
|
||||
setup (props) {
|
||||
const VxeUIFormComponent = VxeUI.getComponent<VxeFormComponent>('VxeForm')
|
||||
const VxeUIFormItemComponent = VxeUI.getComponent<VxeFormItemComponent>('VxeFormItem')
|
||||
const VxeUISwitchComponent = VxeUI.getComponent<VxeSwitchComponent>('VxeSwitch')
|
||||
const VxeUIInputComponent = VxeUI.getComponent<VxeInputComponent>('VxeInput')
|
||||
|
||||
const { renderDataSourceFormItem } = useWidgetPropDataSource(VxeUI, props, false)
|
||||
|
||||
return () => {
|
||||
const { renderParams } = props
|
||||
const { widget } = renderParams
|
||||
|
||||
return h(VxeUIFormComponent, {
|
||||
class: 'vxe-form-design--widget-render-form-wrapper',
|
||||
vertical: true,
|
||||
span: 24,
|
||||
titleBold: true,
|
||||
titleOverflow: true,
|
||||
data: widget.options
|
||||
}, {
|
||||
default () {
|
||||
return [
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.name')
|
||||
}, {
|
||||
default () {
|
||||
return h(VxeUIInputComponent, {
|
||||
modelValue: widget.title,
|
||||
'onUpdate:modelValue' (val: any) {
|
||||
widget.title = val
|
||||
}
|
||||
})
|
||||
}
|
||||
}),
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.placeholder'),
|
||||
field: 'placeholder',
|
||||
itemRender: { name: 'ElInput' }
|
||||
}),
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.required')
|
||||
}, {
|
||||
default () {
|
||||
return h(VxeUISwitchComponent, {
|
||||
modelValue: widget.required,
|
||||
'onUpdate:modelValue' (val: any) {
|
||||
widget.required = val
|
||||
}
|
||||
})
|
||||
}
|
||||
}),
|
||||
renderDataSourceFormItem()
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const WidgetACheckboxViewComponent = defineComponent({
|
||||
props: {
|
||||
renderOpts: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetViewOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
renderParams: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetViewParams<WidgetACheckboxFormObjVO>>,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: [],
|
||||
setup (props) {
|
||||
const VxeUIFormItemComponent = VxeUI.getComponent<VxeFormItemComponent>('VxeFormItem')
|
||||
|
||||
const checkboxOptions = computed(() => {
|
||||
const { renderParams } = props
|
||||
const { widget } = renderParams
|
||||
const { options } = widget
|
||||
return options.options.map(item => {
|
||||
return {
|
||||
label: item.value,
|
||||
value: item.value
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const changeEvent = () => {
|
||||
const { renderParams } = props
|
||||
const { widget, $formView } = renderParams
|
||||
if ($formView) {
|
||||
const itemValue = $formView ? $formView.getItemValue(widget) : null
|
||||
$formView.updateItemStatus(widget, itemValue)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
const { renderParams } = props
|
||||
const { widget, $formView } = renderParams
|
||||
|
||||
return h(VxeUIFormItemComponent, {
|
||||
class: ['vxe-form-design--widget-render-form-item'],
|
||||
field: widget.field,
|
||||
title: widget.title
|
||||
}, {
|
||||
default () {
|
||||
return h(resolveComponent('a-checkbox-group') as ComponentOptions, {
|
||||
value: $formView ? $formView.getItemValue(widget) : null,
|
||||
options: checkboxOptions.value,
|
||||
onChange: changeEvent,
|
||||
'onUpdate:value' (val: any) {
|
||||
if ($formView) {
|
||||
$formView.setItemValue(widget, val)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
getWidgetACheckboxConfig,
|
||||
WidgetACheckboxFormComponent,
|
||||
WidgetACheckboxViewComponent
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
import { defineComponent, h, PropType, resolveComponent, ComponentOptions } from 'vue'
|
||||
|
||||
import type { VxeUIExport, VxeGlobalRendererHandles, VxeFormComponent, VxeFormItemComponent, VxeSwitchComponent, VxeInputComponent } from 'vxe-pc-ui'
|
||||
|
||||
interface WidgetADatePickerFormObjVO {
|
||||
placeholder: string
|
||||
}
|
||||
|
||||
export function createWidgetADatePicker (VxeUI: VxeUIExport) {
|
||||
const getWidgetADatePickerConfig = (params: VxeGlobalRendererHandles.CreateFormDesignWidgetConfigParams): VxeGlobalRendererHandles.CreateFormDesignWidgetConfigObj<WidgetADatePickerFormObjVO> => {
|
||||
return {
|
||||
title: '日期',
|
||||
icon: 'vxe-icon-input',
|
||||
options: {
|
||||
placeholder: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const WidgetADatePickerFormComponent = defineComponent({
|
||||
props: {
|
||||
renderOpts: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetFormViewOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
renderParams: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetFormViewParams<WidgetADatePickerFormObjVO>>,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: [],
|
||||
setup (props) {
|
||||
const VxeUIFormComponent = VxeUI.getComponent<VxeFormComponent>('VxeForm')
|
||||
const VxeUIFormItemComponent = VxeUI.getComponent<VxeFormItemComponent>('VxeFormItem')
|
||||
const VxeUISwitchComponent = VxeUI.getComponent<VxeSwitchComponent>('VxeSwitch')
|
||||
const VxeUIInputComponent = VxeUI.getComponent<VxeInputComponent>('VxeInput')
|
||||
|
||||
return () => {
|
||||
const { renderParams } = props
|
||||
const { widget } = renderParams
|
||||
|
||||
return h(VxeUIFormComponent, {
|
||||
class: 'vxe-form-design--widget-render-form-wrapper',
|
||||
vertical: true,
|
||||
span: 24,
|
||||
titleBold: true,
|
||||
titleOverflow: true,
|
||||
data: widget.options
|
||||
}, {
|
||||
default () {
|
||||
return [
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.name')
|
||||
}, {
|
||||
default () {
|
||||
return h(VxeUIInputComponent, {
|
||||
modelValue: widget.title,
|
||||
'onUpdate:modelValue' (val: any) {
|
||||
widget.title = val
|
||||
}
|
||||
})
|
||||
}
|
||||
}),
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.placeholder'),
|
||||
field: 'placeholder',
|
||||
itemRender: { name: 'VxeInput' }
|
||||
}),
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.required')
|
||||
}, {
|
||||
default () {
|
||||
return h(VxeUISwitchComponent, {
|
||||
modelValue: widget.required,
|
||||
'onUpdate:modelValue' (val: any) {
|
||||
widget.required = val
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const WidgetADatePickerViewComponent = defineComponent({
|
||||
props: {
|
||||
renderOpts: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetViewOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
renderParams: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetViewParams<WidgetADatePickerFormObjVO>>,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: [],
|
||||
setup (props) {
|
||||
const VxeUIFormItemComponent = VxeUI.getComponent<VxeFormItemComponent>('VxeFormItem')
|
||||
|
||||
const changeEvent = () => {
|
||||
const { renderParams } = props
|
||||
const { widget, $formView } = renderParams
|
||||
if ($formView) {
|
||||
const itemValue = $formView ? $formView.getItemValue(widget) : null
|
||||
$formView.updateItemStatus(widget, itemValue)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
const { renderParams } = props
|
||||
const { widget, $formView } = renderParams
|
||||
const { options } = widget
|
||||
|
||||
return h(VxeUIFormItemComponent, {
|
||||
class: ['vxe-form-design--widget-render-form-item'],
|
||||
field: widget.field,
|
||||
title: widget.title
|
||||
}, {
|
||||
default () {
|
||||
return h(resolveComponent('a-date-picker') as ComponentOptions, {
|
||||
value: $formView ? $formView.getItemValue(widget) : null,
|
||||
placeholder: options.placeholder,
|
||||
onChange: changeEvent,
|
||||
'onUpdate:value' (val: any) {
|
||||
if ($formView) {
|
||||
$formView.setItemValue(widget, val)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
getWidgetADatePickerConfig,
|
||||
WidgetADatePickerFormComponent,
|
||||
WidgetADatePickerViewComponent
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
import { h } from 'vue'
|
||||
import { createWidgetAInput } from './input-widget'
|
||||
import { createWidgetATextarea } from './textarea-widget'
|
||||
import { createWidgetAInputNumber } from './number-input-widget'
|
||||
import { createWidgetADatePicker } from './date-picker-widget'
|
||||
import { createWidgetASelect } from './select-widget'
|
||||
import { createWidgetARadio } from './radio-widget'
|
||||
import { createWidgetACheckbox } from './checkbox-widget'
|
||||
import { createWidgetASwitch } from './switch-widget'
|
||||
|
||||
import type { VxeUIExport } from 'vxe-pc-ui'
|
||||
|
||||
/**
|
||||
* 表单设计器 - 渲染器
|
||||
*/
|
||||
export function defineFormDesignRender (VxeUI: VxeUIExport) {
|
||||
const { getWidgetAInputConfig, WidgetAInputViewComponent, WidgetAInputFormComponent } = createWidgetAInput(VxeUI)
|
||||
const { getWidgetATextareaConfig, WidgetATextareaViewComponent, WidgetATextareaFormComponent } = createWidgetATextarea(VxeUI)
|
||||
const { getWidgetAInputNumberConfig, WidgetAInputNumberViewComponent, WidgetAInputNumberFormComponent } = createWidgetAInputNumber(VxeUI)
|
||||
const { getWidgetADatePickerConfig, WidgetADatePickerViewComponent, WidgetADatePickerFormComponent } = createWidgetADatePicker(VxeUI)
|
||||
const { getWidgetASelectConfig, WidgetASelectViewComponent, WidgetASelectFormComponent } = createWidgetASelect(VxeUI)
|
||||
const { getWidgetARadioConfig, WidgetARadioViewComponent, WidgetARadioFormComponent } = createWidgetARadio(VxeUI)
|
||||
const { getWidgetACheckboxConfig, WidgetACheckboxViewComponent, WidgetACheckboxFormComponent } = createWidgetACheckbox(VxeUI)
|
||||
const { getWidgetASwitchConfig, WidgetASwitchViewComponent, WidgetASwitchFormComponent } = createWidgetASwitch(VxeUI)
|
||||
|
||||
VxeUI.renderer.mixin({
|
||||
AInputWidget: {
|
||||
createFormDesignWidgetConfig: getWidgetAInputConfig,
|
||||
renderFormDesignWidgetView (renderOpts, renderParams) {
|
||||
return h(WidgetAInputViewComponent, { renderOpts, renderParams })
|
||||
},
|
||||
renderFormDesignWidgetFormView (renderOpts, renderParams) {
|
||||
return h(WidgetAInputFormComponent, { renderOpts, renderParams })
|
||||
}
|
||||
},
|
||||
ATextareaWidget: {
|
||||
createFormDesignWidgetConfig: getWidgetATextareaConfig,
|
||||
renderFormDesignWidgetView (renderOpts, renderParams) {
|
||||
return h(WidgetATextareaViewComponent, { renderOpts, renderParams })
|
||||
},
|
||||
renderFormDesignWidgetFormView (renderOpts, renderParams) {
|
||||
return h(WidgetATextareaFormComponent, { renderOpts, renderParams })
|
||||
}
|
||||
},
|
||||
ANumberInputWidget: {
|
||||
createFormDesignWidgetConfig: getWidgetAInputNumberConfig,
|
||||
renderFormDesignWidgetView (renderOpts, renderParams) {
|
||||
return h(WidgetAInputNumberViewComponent, { renderOpts, renderParams })
|
||||
},
|
||||
renderFormDesignWidgetFormView (renderOpts, renderParams) {
|
||||
return h(WidgetAInputNumberFormComponent, { renderOpts, renderParams })
|
||||
}
|
||||
},
|
||||
ADatePickerWidget: {
|
||||
createFormDesignWidgetConfig: getWidgetADatePickerConfig,
|
||||
renderFormDesignWidgetView (renderOpts, renderParams) {
|
||||
return h(WidgetADatePickerViewComponent, { renderOpts, renderParams })
|
||||
},
|
||||
renderFormDesignWidgetFormView (renderOpts, renderParams) {
|
||||
return h(WidgetADatePickerFormComponent, { renderOpts, renderParams })
|
||||
}
|
||||
},
|
||||
ASelectWidget: {
|
||||
createFormDesignWidgetConfig: getWidgetASelectConfig,
|
||||
renderFormDesignWidgetView (renderOpts, renderParams) {
|
||||
return h(WidgetASelectViewComponent, { renderOpts, renderParams })
|
||||
},
|
||||
renderFormDesignWidgetFormView (renderOpts, renderParams) {
|
||||
return h(WidgetASelectFormComponent, { renderOpts, renderParams })
|
||||
}
|
||||
},
|
||||
ARadioWidget: {
|
||||
createFormDesignWidgetConfig: getWidgetARadioConfig,
|
||||
renderFormDesignWidgetView (renderOpts, renderParams) {
|
||||
return h(WidgetARadioViewComponent, { renderOpts, renderParams })
|
||||
},
|
||||
renderFormDesignWidgetFormView (renderOpts, renderParams) {
|
||||
return h(WidgetARadioFormComponent, { renderOpts, renderParams })
|
||||
}
|
||||
},
|
||||
ACheckboxWidget: {
|
||||
createFormDesignWidgetConfig: getWidgetACheckboxConfig,
|
||||
createFormDesignWidgetFieldValue: () => [],
|
||||
renderFormDesignWidgetView (renderOpts: any, renderParams: any) {
|
||||
return h(WidgetACheckboxViewComponent, { renderOpts, renderParams })
|
||||
},
|
||||
renderFormDesignWidgetFormView (renderOpts: any, renderParams: any) {
|
||||
return h(WidgetACheckboxFormComponent, { renderOpts, renderParams })
|
||||
}
|
||||
},
|
||||
ASwitchWidget: {
|
||||
createFormDesignWidgetConfig: getWidgetASwitchConfig,
|
||||
renderFormDesignWidgetView (renderOpts, renderParams) {
|
||||
return h(WidgetASwitchViewComponent, { renderOpts, renderParams })
|
||||
},
|
||||
renderFormDesignWidgetFormView (renderOpts, renderParams) {
|
||||
return h(WidgetASwitchFormComponent, { renderOpts, renderParams })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
import { defineComponent, h, PropType, resolveComponent, ComponentOptions } from 'vue'
|
||||
|
||||
import type { VxeUIExport, VxeGlobalRendererHandles, VxeFormComponent, VxeFormItemComponent, VxeSwitchComponent, VxeInputComponent } from 'vxe-pc-ui'
|
||||
|
||||
interface WidgetAInputFormObjVO {
|
||||
placeholder: string
|
||||
}
|
||||
|
||||
export function createWidgetAInput(VxeUI: VxeUIExport) {
|
||||
const getWidgetAInputConfig = (params: VxeGlobalRendererHandles.CreateFormDesignWidgetConfigParams): VxeGlobalRendererHandles.CreateFormDesignWidgetConfigObj<WidgetAInputFormObjVO> => {
|
||||
return {
|
||||
title: '输入框',
|
||||
icon: 'vxe-icon-input',
|
||||
options: {
|
||||
placeholder: ''
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const WidgetAInputFormComponent = defineComponent({
|
||||
props: {
|
||||
renderOpts: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetFormViewOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
renderParams: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetFormViewParams<WidgetAInputFormObjVO>>,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: [],
|
||||
setup(props) {
|
||||
const VxeUIFormComponent = VxeUI.getComponent<VxeFormComponent>('VxeForm')
|
||||
const VxeUIFormItemComponent = VxeUI.getComponent<VxeFormItemComponent>('VxeFormItem')
|
||||
const VxeUISwitchComponent = VxeUI.getComponent<VxeSwitchComponent>('VxeSwitch')
|
||||
const VxeUIInputComponent = VxeUI.getComponent<VxeInputComponent>('VxeInput')
|
||||
|
||||
return () => {
|
||||
const { renderParams } = props
|
||||
const { widget } = renderParams
|
||||
const { options } = widget
|
||||
|
||||
return h(VxeUIFormComponent, {
|
||||
class: 'vxe-form-design--widget-render-form-wrapper',
|
||||
vertical: true,
|
||||
span: 24,
|
||||
titleBold: true,
|
||||
titleOverflow: true,
|
||||
data: options
|
||||
}, {
|
||||
default() {
|
||||
return [
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.name')
|
||||
}, {
|
||||
default() {
|
||||
return h(VxeUIInputComponent, {
|
||||
modelValue: widget.title,
|
||||
placeholder: options.placeholder,
|
||||
'onUpdate:modelValue'(val: any) {
|
||||
widget.title = val
|
||||
}
|
||||
})
|
||||
}
|
||||
}),
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.placeholder'),
|
||||
field: 'placeholder',
|
||||
itemRender: { name: 'VxeInput' }
|
||||
}),
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.required')
|
||||
}, {
|
||||
default() {
|
||||
return h(VxeUISwitchComponent, {
|
||||
modelValue: widget.required,
|
||||
'onUpdate:modelValue'(val: any) {
|
||||
widget.required = val
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const WidgetAInputViewComponent = defineComponent({
|
||||
props: {
|
||||
renderOpts: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetViewOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
renderParams: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetViewParams<WidgetAInputFormObjVO>>,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: [],
|
||||
setup(props) {
|
||||
const VxeUIFormItemComponent = VxeUI.getComponent<VxeFormItemComponent>('VxeFormItem')
|
||||
|
||||
const changeEvent = () => {
|
||||
const { renderParams } = props
|
||||
const { widget, $formView } = renderParams
|
||||
if ($formView) {
|
||||
const itemValue = $formView ? $formView.getItemValue(widget) : null
|
||||
$formView.updateItemStatus(widget, itemValue)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
const { renderParams } = props
|
||||
const { widget, $formView } = renderParams
|
||||
|
||||
return h(VxeUIFormItemComponent, {
|
||||
class: ['vxe-form-design--widget-render-form-item'],
|
||||
field: widget.field,
|
||||
title: widget.title
|
||||
}, {
|
||||
default() {
|
||||
return h(resolveComponent('n-input') as ComponentOptions, {
|
||||
value: $formView ? $formView.getItemValue(widget) : null,
|
||||
onChange: changeEvent,
|
||||
'onUpdate:value'(val: any) {
|
||||
if ($formView) {
|
||||
$formView.setItemValue(widget, val)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
getWidgetAInputConfig,
|
||||
WidgetAInputFormComponent,
|
||||
WidgetAInputViewComponent
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
import { defineComponent, h, PropType, resolveComponent, ComponentOptions } from 'vue'
|
||||
|
||||
import type { VxeUIExport, VxeGlobalRendererHandles, VxeFormComponent, VxeFormItemComponent, VxeSwitchComponent, VxeInputComponent } from 'vxe-pc-ui'
|
||||
|
||||
interface WidgetAInputNumberFormObjVO {
|
||||
placeholder: string
|
||||
}
|
||||
|
||||
export function createWidgetAInputNumber(VxeUI: VxeUIExport) {
|
||||
const getWidgetAInputNumberConfig = (params: VxeGlobalRendererHandles.CreateFormDesignWidgetConfigParams): VxeGlobalRendererHandles.CreateFormDesignWidgetConfigObj<WidgetAInputNumberFormObjVO> => {
|
||||
return {
|
||||
title: '数字',
|
||||
icon: 'vxe-icon-number',
|
||||
options: {
|
||||
placeholder: '请输入'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const WidgetAInputNumberFormComponent = defineComponent({
|
||||
props: {
|
||||
renderOpts: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetFormViewOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
renderParams: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetFormViewParams<WidgetAInputNumberFormObjVO>>,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: [],
|
||||
setup(props) {
|
||||
const VxeUIFormComponent = VxeUI.getComponent<VxeFormComponent>('VxeForm')
|
||||
const VxeUIFormItemComponent = VxeUI.getComponent<VxeFormItemComponent>('VxeFormItem')
|
||||
const VxeUISwitchComponent = VxeUI.getComponent<VxeSwitchComponent>('VxeSwitch')
|
||||
const VxeUIInputComponent = VxeUI.getComponent<VxeInputComponent>('VxeInput')
|
||||
|
||||
return () => {
|
||||
const { renderParams } = props
|
||||
const { widget } = renderParams
|
||||
|
||||
return h(VxeUIFormComponent, {
|
||||
class: 'vxe-form-design--widget-render-form-wrapper',
|
||||
vertical: true,
|
||||
span: 24,
|
||||
titleBold: true,
|
||||
titleOverflow: true,
|
||||
data: widget.options
|
||||
}, {
|
||||
default() {
|
||||
return [
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.name')
|
||||
}, {
|
||||
default() {
|
||||
return h(VxeUIInputComponent, {
|
||||
modelValue: widget.title,
|
||||
'onUpdate:modelValue'(val: any) {
|
||||
widget.title = val
|
||||
}
|
||||
})
|
||||
}
|
||||
}),
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.placeholder'),
|
||||
field: 'placeholder',
|
||||
itemRender: { name: 'ElInput' }
|
||||
}),
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.required')
|
||||
}, {
|
||||
default() {
|
||||
return h(VxeUISwitchComponent, {
|
||||
modelValue: widget.required,
|
||||
'onUpdate:modelValue'(val: any) {
|
||||
widget.required = val
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const WidgetAInputNumberViewComponent = defineComponent({
|
||||
props: {
|
||||
renderOpts: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetViewOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
renderParams: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetViewParams<WidgetAInputNumberFormObjVO>>,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: [],
|
||||
setup(props) {
|
||||
const VxeUIFormItemComponent = VxeUI.getComponent<VxeFormItemComponent>('VxeFormItem')
|
||||
|
||||
const changeEvent = () => {
|
||||
const { renderParams } = props
|
||||
const { widget, $formView } = renderParams
|
||||
if ($formView) {
|
||||
const itemValue = $formView ? $formView.getItemValue(widget) : null
|
||||
$formView.updateItemStatus(widget, itemValue)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
const { renderParams } = props
|
||||
const { widget, $formView } = renderParams
|
||||
const { options } = widget
|
||||
|
||||
return h(VxeUIFormItemComponent, {
|
||||
class: ['vxe-form-design--widget-render-form-item'],
|
||||
field: widget.field,
|
||||
title: widget.title
|
||||
}, {
|
||||
default() {
|
||||
return h(resolveComponent('n-input-number') as ComponentOptions, {
|
||||
value: $formView ? $formView.getItemValue(widget) : null,
|
||||
placeholder: options.placeholder,
|
||||
onChange: changeEvent,
|
||||
'onUpdate:value'(val: any) {
|
||||
if ($formView) {
|
||||
$formView.setItemValue(widget, val)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
getWidgetAInputNumberConfig,
|
||||
WidgetAInputNumberFormComponent,
|
||||
WidgetAInputNumberViewComponent
|
||||
}
|
||||
}
|
|
@ -0,0 +1,164 @@
|
|||
import { defineComponent, h, PropType, resolveComponent, ComponentOptions, computed } from 'vue'
|
||||
import { useWidgetPropDataSource, WidgetDataSourceOptionObjVO } from './use'
|
||||
import XEUtils from 'xe-utils'
|
||||
|
||||
import type { VxeUIExport, VxeGlobalRendererHandles, VxeFormComponent, VxeFormItemComponent, VxeSwitchComponent, VxeInputComponent } from 'vxe-pc-ui'
|
||||
|
||||
interface WidgetARadioFormObjVO {
|
||||
options: WidgetDataSourceOptionObjVO[]
|
||||
}
|
||||
|
||||
export function createWidgetARadio (VxeUI: VxeUIExport) {
|
||||
const getWidgetARadioConfig = (params: VxeGlobalRendererHandles.CreateFormDesignWidgetConfigParams): VxeGlobalRendererHandles.CreateFormDesignWidgetConfigObj<WidgetARadioFormObjVO> => {
|
||||
return {
|
||||
title: '单选框',
|
||||
icon: 'vxe-icon-radio-checked',
|
||||
options: {
|
||||
options: XEUtils.range(0, 3).map((v, i) => {
|
||||
return {
|
||||
value: VxeUI.getI18n('vxe.formDesign.widgetProp.dataSource.defValue', [i + 1])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const WidgetARadioFormComponent = defineComponent({
|
||||
props: {
|
||||
renderOpts: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetFormViewOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
renderParams: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetFormViewParams<WidgetARadioFormObjVO>>,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: [],
|
||||
setup (props) {
|
||||
const VxeUIFormComponent = VxeUI.getComponent<VxeFormComponent>('VxeForm')
|
||||
const VxeUIFormItemComponent = VxeUI.getComponent<VxeFormItemComponent>('VxeFormItem')
|
||||
const VxeUISwitchComponent = VxeUI.getComponent<VxeSwitchComponent>('VxeSwitch')
|
||||
const VxeUIInputComponent = VxeUI.getComponent<VxeInputComponent>('VxeInput')
|
||||
|
||||
const { renderDataSourceFormItem } = useWidgetPropDataSource(VxeUI, props, false)
|
||||
|
||||
return () => {
|
||||
const { renderParams } = props
|
||||
const { widget } = renderParams
|
||||
|
||||
return h(VxeUIFormComponent, {
|
||||
class: 'vxe-form-design--widget-render-form-wrapper',
|
||||
vertical: true,
|
||||
span: 24,
|
||||
titleBold: true,
|
||||
titleOverflow: true,
|
||||
data: widget.options
|
||||
}, {
|
||||
default () {
|
||||
return [
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.name')
|
||||
}, {
|
||||
default () {
|
||||
return h(VxeUIInputComponent, {
|
||||
modelValue: widget.title,
|
||||
'onUpdate:modelValue' (val: any) {
|
||||
widget.title = val
|
||||
}
|
||||
})
|
||||
}
|
||||
}),
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.placeholder'),
|
||||
field: 'placeholder',
|
||||
itemRender: { name: 'ElInput' }
|
||||
}),
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.required')
|
||||
}, {
|
||||
default () {
|
||||
return h(VxeUISwitchComponent, {
|
||||
modelValue: widget.required,
|
||||
'onUpdate:modelValue' (val: any) {
|
||||
widget.required = val
|
||||
}
|
||||
})
|
||||
}
|
||||
}),
|
||||
renderDataSourceFormItem()
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const WidgetARadioViewComponent = defineComponent({
|
||||
props: {
|
||||
renderOpts: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetViewOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
renderParams: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetViewParams<WidgetARadioFormObjVO>>,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: [],
|
||||
setup (props) {
|
||||
const VxeUIFormItemComponent = VxeUI.getComponent<VxeFormItemComponent>('VxeFormItem')
|
||||
|
||||
const radioOptions = computed(() => {
|
||||
const { renderParams } = props
|
||||
const { widget } = renderParams
|
||||
const { options } = widget
|
||||
return options.options.map(item => {
|
||||
return {
|
||||
label: item.value,
|
||||
value: item.value
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
const changeEvent = () => {
|
||||
const { renderParams } = props
|
||||
const { widget, $formView } = renderParams
|
||||
if ($formView) {
|
||||
const itemValue = $formView ? $formView.getItemValue(widget) : null
|
||||
$formView.updateItemStatus(widget, itemValue)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
const { renderParams } = props
|
||||
const { widget, $formView } = renderParams
|
||||
|
||||
return h(VxeUIFormItemComponent, {
|
||||
class: ['vxe-form-design--widget-render-form-item'],
|
||||
field: widget.field,
|
||||
title: widget.title
|
||||
}, {
|
||||
default () {
|
||||
return h(resolveComponent('a-radio-group') as ComponentOptions, {
|
||||
value: $formView ? $formView.getItemValue(widget) : null,
|
||||
options: radioOptions.value,
|
||||
onChange: changeEvent,
|
||||
'onUpdate:value' (val: any) {
|
||||
if ($formView) {
|
||||
$formView.setItemValue(widget, val)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
getWidgetARadioConfig,
|
||||
WidgetARadioFormComponent,
|
||||
WidgetARadioViewComponent
|
||||
}
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
import { defineComponent, h, PropType, resolveComponent, ComponentOptions } from 'vue'
|
||||
import { useWidgetPropDataSource, WidgetDataSourceOptionObjVO } from './use'
|
||||
import XEUtils from 'xe-utils'
|
||||
|
||||
import type { VxeUIExport, VxeGlobalRendererHandles, VxeFormComponent, VxeFormItemComponent, VxeSwitchComponent, VxeInputComponent } from 'vxe-pc-ui'
|
||||
|
||||
interface WidgetASelectFormObjVO {
|
||||
placeholder: string
|
||||
options: WidgetDataSourceOptionObjVO[]
|
||||
}
|
||||
|
||||
export function createWidgetASelect(VxeUI: VxeUIExport) {
|
||||
const getWidgetASelectConfig = (params: VxeGlobalRendererHandles.CreateFormDesignWidgetConfigParams): VxeGlobalRendererHandles.CreateFormDesignWidgetConfigObj<WidgetASelectFormObjVO> => {
|
||||
return {
|
||||
title: '下拉框',
|
||||
icon: 'vxe-icon-select',
|
||||
options: {
|
||||
placeholder: '请选择',
|
||||
options: XEUtils.range(0, 3).map((v, i) => {
|
||||
return {
|
||||
value: VxeUI.getI18n('vxe.formDesign.widgetProp.dataSource.defValue', [i + 1])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const WidgetASelectFormComponent = defineComponent({
|
||||
props: {
|
||||
renderOpts: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetFormViewOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
renderParams: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetFormViewParams<WidgetASelectFormObjVO>>,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: [],
|
||||
setup(props) {
|
||||
const VxeUIFormComponent = VxeUI.getComponent<VxeFormComponent>('VxeForm')
|
||||
const VxeUIFormItemComponent = VxeUI.getComponent<VxeFormItemComponent>('VxeFormItem')
|
||||
const VxeUISwitchComponent = VxeUI.getComponent<VxeSwitchComponent>('VxeSwitch')
|
||||
const VxeUIInputComponent = VxeUI.getComponent<VxeInputComponent>('VxeInput')
|
||||
|
||||
const { renderDataSourceFormItem } = useWidgetPropDataSource(VxeUI, props, false)
|
||||
|
||||
return () => {
|
||||
const { renderParams } = props
|
||||
const { widget } = renderParams
|
||||
|
||||
return h(VxeUIFormComponent, {
|
||||
class: 'vxe-form-design--widget-render-form-wrapper',
|
||||
vertical: true,
|
||||
span: 24,
|
||||
titleBold: true,
|
||||
titleOverflow: true,
|
||||
data: widget.options
|
||||
}, {
|
||||
default() {
|
||||
return [
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.name')
|
||||
}, {
|
||||
default() {
|
||||
return h(VxeUIInputComponent, {
|
||||
modelValue: widget.title,
|
||||
'onUpdate:modelValue'(val: any) {
|
||||
widget.title = val
|
||||
}
|
||||
})
|
||||
}
|
||||
}),
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.placeholder'),
|
||||
field: 'placeholder',
|
||||
itemRender: { name: 'ElInput' }
|
||||
}),
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.required')
|
||||
}, {
|
||||
default() {
|
||||
return h(VxeUISwitchComponent, {
|
||||
modelValue: widget.required,
|
||||
'onUpdate:modelValue'(val: any) {
|
||||
widget.required = val
|
||||
}
|
||||
})
|
||||
}
|
||||
}),
|
||||
renderDataSourceFormItem()
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const WidgetASelectViewComponent = defineComponent({
|
||||
props: {
|
||||
renderOpts: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetViewOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
renderParams: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetViewParams<WidgetASelectFormObjVO>>,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: [],
|
||||
setup(props) {
|
||||
const VxeUIFormItemComponent = VxeUI.getComponent<VxeFormItemComponent>('VxeFormItem')
|
||||
|
||||
const changeEvent = () => {
|
||||
const { renderParams } = props
|
||||
const { widget, $formView } = renderParams
|
||||
if ($formView) {
|
||||
const itemValue = $formView ? $formView.getItemValue(widget) : null
|
||||
$formView.updateItemStatus(widget, itemValue)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
const { renderParams } = props
|
||||
const { widget, $formView } = renderParams
|
||||
const { options } = widget
|
||||
|
||||
return h(VxeUIFormItemComponent, {
|
||||
class: ['vxe-form-design--widget-render-form-item'],
|
||||
field: widget.field,
|
||||
title: widget.title
|
||||
}, {
|
||||
default() {
|
||||
return h(resolveComponent('n-select') as ComponentOptions, {
|
||||
value: $formView ? $formView.getItemValue(widget) : null,
|
||||
options: options.options,
|
||||
placeholder: options.placeholder,
|
||||
onChange: changeEvent,
|
||||
'onUpdate:value'(val: any) {
|
||||
if ($formView) {
|
||||
$formView.setItemValue(widget, val)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
getWidgetASelectConfig,
|
||||
WidgetASelectFormComponent,
|
||||
WidgetASelectViewComponent
|
||||
}
|
||||
}
|
|
@ -0,0 +1,140 @@
|
|||
import { defineComponent, h, PropType, resolveComponent, ComponentOptions } from 'vue'
|
||||
|
||||
import type { VxeUIExport, VxeGlobalRendererHandles, VxeFormComponent, VxeFormItemComponent, VxeSwitchComponent, VxeInputComponent } from 'vxe-pc-ui'
|
||||
|
||||
interface WidgetASwitchFormObjVO {
|
||||
}
|
||||
|
||||
export function createWidgetASwitch (VxeUI: VxeUIExport) {
|
||||
const getWidgetASwitchConfig = (params: VxeGlobalRendererHandles.CreateFormDesignWidgetConfigParams): VxeGlobalRendererHandles.CreateFormDesignWidgetConfigObj<WidgetASwitchFormObjVO> => {
|
||||
return {
|
||||
title: '是/否',
|
||||
icon: 'vxe-icon-switch',
|
||||
options: {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const WidgetASwitchFormComponent = defineComponent({
|
||||
props: {
|
||||
renderOpts: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetFormViewOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
renderParams: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetFormViewParams<WidgetASwitchFormObjVO>>,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: [],
|
||||
setup (props) {
|
||||
const VxeUIFormComponent = VxeUI.getComponent<VxeFormComponent>('VxeForm')
|
||||
const VxeUIFormItemComponent = VxeUI.getComponent<VxeFormItemComponent>('VxeFormItem')
|
||||
const VxeUISwitchComponent = VxeUI.getComponent<VxeSwitchComponent>('VxeSwitch')
|
||||
const VxeUIInputComponent = VxeUI.getComponent<VxeInputComponent>('VxeInput')
|
||||
|
||||
return () => {
|
||||
const { renderParams } = props
|
||||
const { widget } = renderParams
|
||||
|
||||
return h(VxeUIFormComponent, {
|
||||
class: 'vxe-form-design--widget-render-form-wrapper',
|
||||
vertical: true,
|
||||
span: 24,
|
||||
titleBold: true,
|
||||
titleOverflow: true,
|
||||
data: widget.options
|
||||
}, {
|
||||
default () {
|
||||
return [
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.name')
|
||||
}, {
|
||||
default () {
|
||||
return h(VxeUIInputComponent, {
|
||||
modelValue: widget.title,
|
||||
'onUpdate:modelValue' (val: any) {
|
||||
widget.title = val
|
||||
}
|
||||
})
|
||||
}
|
||||
}),
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.placeholder'),
|
||||
field: 'placeholder',
|
||||
itemRender: { name: 'ElInput' }
|
||||
}),
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.required')
|
||||
}, {
|
||||
default () {
|
||||
return h(VxeUISwitchComponent, {
|
||||
modelValue: widget.required,
|
||||
'onUpdate:modelValue' (val: any) {
|
||||
widget.required = val
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const WidgetASwitchViewComponent = defineComponent({
|
||||
props: {
|
||||
renderOpts: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetViewOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
renderParams: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetViewParams<WidgetASwitchFormObjVO>>,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: [],
|
||||
setup (props) {
|
||||
const VxeUIFormItemComponent = VxeUI.getComponent<VxeFormItemComponent>('VxeFormItem')
|
||||
|
||||
const changeEvent = () => {
|
||||
const { renderParams } = props
|
||||
const { widget, $formView } = renderParams
|
||||
if ($formView) {
|
||||
const itemValue = $formView ? $formView.getItemValue(widget) : null
|
||||
$formView.updateItemStatus(widget, itemValue)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
const { renderParams } = props
|
||||
const { widget, $formView } = renderParams
|
||||
|
||||
return h(VxeUIFormItemComponent, {
|
||||
class: ['vxe-form-design--widget-render-form-item'],
|
||||
field: widget.field,
|
||||
title: widget.title
|
||||
}, {
|
||||
default () {
|
||||
return h(resolveComponent('a-switch') as ComponentOptions, {
|
||||
checked: $formView ? $formView.getItemValue(widget) : null,
|
||||
onChange: changeEvent,
|
||||
'onUpdate:checked' (val: any) {
|
||||
if ($formView) {
|
||||
$formView.setItemValue(widget, val)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
getWidgetASwitchConfig,
|
||||
WidgetASwitchFormComponent,
|
||||
WidgetASwitchViewComponent
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
import { defineComponent, h, PropType, resolveComponent, ComponentOptions } from 'vue'
|
||||
|
||||
import type { VxeUIExport, VxeGlobalRendererHandles, VxeFormComponent, VxeFormItemComponent, VxeSwitchComponent, VxeInputComponent } from 'vxe-pc-ui'
|
||||
|
||||
interface WidgetATextareaFormObjVO {
|
||||
placeholder: string
|
||||
}
|
||||
|
||||
export function createWidgetATextarea (VxeUI: VxeUIExport) {
|
||||
const getWidgetATextareaConfig = (params: VxeGlobalRendererHandles.CreateFormDesignWidgetConfigParams): VxeGlobalRendererHandles.CreateFormDesignWidgetConfigObj<WidgetATextareaFormObjVO> => {
|
||||
return {
|
||||
title: '文本域',
|
||||
icon: 'vxe-icon-textarea',
|
||||
options: {
|
||||
placeholder: '请输入'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const WidgetATextareaFormComponent = defineComponent({
|
||||
props: {
|
||||
renderOpts: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetFormViewOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
renderParams: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetFormViewParams<WidgetATextareaFormObjVO>>,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: [],
|
||||
setup (props) {
|
||||
const VxeUIFormComponent = VxeUI.getComponent<VxeFormComponent>('VxeForm')
|
||||
const VxeUIFormItemComponent = VxeUI.getComponent<VxeFormItemComponent>('VxeFormItem')
|
||||
const VxeUISwitchComponent = VxeUI.getComponent<VxeSwitchComponent>('VxeSwitch')
|
||||
const VxeUIInputComponent = VxeUI.getComponent<VxeInputComponent>('VxeInput')
|
||||
|
||||
return () => {
|
||||
const { renderParams } = props
|
||||
const { widget } = renderParams
|
||||
|
||||
return h(VxeUIFormComponent, {
|
||||
class: 'vxe-form-design--widget-render-form-wrapper',
|
||||
vertical: true,
|
||||
span: 24,
|
||||
titleBold: true,
|
||||
titleOverflow: true,
|
||||
data: widget.options
|
||||
}, {
|
||||
default () {
|
||||
return [
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.name')
|
||||
}, {
|
||||
default () {
|
||||
return h(VxeUIInputComponent, {
|
||||
modelValue: widget.title,
|
||||
'onUpdate:modelValue' (val: any) {
|
||||
widget.title = val
|
||||
}
|
||||
})
|
||||
}
|
||||
}),
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.placeholder'),
|
||||
field: 'placeholder',
|
||||
itemRender: { name: 'ElInput' }
|
||||
}),
|
||||
h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.required')
|
||||
}, {
|
||||
default () {
|
||||
return h(VxeUISwitchComponent, {
|
||||
modelValue: widget.required,
|
||||
'onUpdate:modelValue' (val: any) {
|
||||
widget.required = val
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const WidgetATextareaViewComponent = defineComponent({
|
||||
props: {
|
||||
renderOpts: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetViewOptions>,
|
||||
default: () => ({})
|
||||
},
|
||||
renderParams: {
|
||||
type: Object as PropType<VxeGlobalRendererHandles.RenderFormDesignWidgetViewParams<WidgetATextareaFormObjVO>>,
|
||||
default: () => ({})
|
||||
}
|
||||
},
|
||||
emits: [],
|
||||
setup (props) {
|
||||
const VxeUIFormItemComponent = VxeUI.getComponent<VxeFormItemComponent>('VxeFormItem')
|
||||
|
||||
const changeEvent = () => {
|
||||
const { renderParams } = props
|
||||
const { widget, $formView } = renderParams
|
||||
if ($formView) {
|
||||
const itemValue = $formView ? $formView.getItemValue(widget) : null
|
||||
$formView.updateItemStatus(widget, itemValue)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
const { renderParams } = props
|
||||
const { widget, $formView } = renderParams
|
||||
const { options } = widget
|
||||
|
||||
return h(VxeUIFormItemComponent, {
|
||||
class: ['vxe-form-design--widget-render-form-item'],
|
||||
field: widget.field,
|
||||
title: widget.title
|
||||
}, {
|
||||
default () {
|
||||
return h(resolveComponent('a-textarea') as ComponentOptions, {
|
||||
value: $formView ? $formView.getItemValue(widget) : null,
|
||||
placeholder: options.placeholder,
|
||||
autoSize: {
|
||||
minRows: 2,
|
||||
maxRows: 4
|
||||
},
|
||||
onChange: changeEvent,
|
||||
'onUpdate:value' (val: any) {
|
||||
if ($formView) {
|
||||
$formView.setItemValue(widget, val)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
getWidgetATextareaConfig,
|
||||
WidgetATextareaFormComponent,
|
||||
WidgetATextareaViewComponent
|
||||
}
|
||||
}
|
|
@ -0,0 +1,255 @@
|
|||
import { VNode, h, onMounted, ref, watch } from 'vue'
|
||||
|
||||
import type { VxeUIExport, VxeGlobalRendererHandles, VxeFormItemComponent, VxeButtonComponent, VxeTextareaComponent, VxeTipComponent } from 'vxe-pc-ui'
|
||||
|
||||
export interface WidgetDataSourceOptionSubObjVO {
|
||||
value: string,
|
||||
}
|
||||
|
||||
export interface WidgetDataSourceOptionObjVO {
|
||||
value: string,
|
||||
options?: WidgetDataSourceOptionSubObjVO[]
|
||||
}
|
||||
|
||||
export function useWidgetPropDataSource (VxeUI: VxeUIExport, props: {
|
||||
readonly renderOpts: VxeGlobalRendererHandles.RenderFormDesignWidgetFormViewOptions;
|
||||
readonly renderParams: VxeGlobalRendererHandles.RenderFormDesignWidgetFormViewParams<{
|
||||
options: WidgetDataSourceOptionObjVO[]
|
||||
}>;
|
||||
}, isSubOption: boolean) {
|
||||
const VxeUIFormItemComponent = VxeUI.getComponent<VxeFormItemComponent>('VxeFormItem')
|
||||
const VxeUIButtonComponent = VxeUI.getComponent<VxeButtonComponent>('VxeButton')
|
||||
const VxeUITextareaComponent = VxeUI.getComponent<VxeTextareaComponent>('VxeTextarea')
|
||||
const VxeUITipComponent = VxeUI.getComponent<VxeTipComponent>('VxeTip')
|
||||
|
||||
const optionsContent = ref('')
|
||||
const expandIndexList = ref<number[]>([])
|
||||
|
||||
const addOptionEvent = () => {
|
||||
const { renderParams } = props
|
||||
const { widget } = renderParams
|
||||
const options = widget.options.options || []
|
||||
options.push({
|
||||
value: VxeUI.getI18n('vxe.formDesign.widgetProp.dataSource.defValue', [options.length + 1])
|
||||
})
|
||||
widget.options.options = [...options]
|
||||
}
|
||||
|
||||
const subRE = /^(\s|\t)+/
|
||||
|
||||
const hasSubOption = (str: string) => {
|
||||
return subRE.test(str)
|
||||
}
|
||||
|
||||
const expandAllOption = () => {
|
||||
const { renderParams } = props
|
||||
const { widget } = renderParams
|
||||
const options = widget.options.options || []
|
||||
const indexList: number[] = []
|
||||
options.forEach((group, gIndex) => {
|
||||
const { options } = group
|
||||
if (options && options.length) {
|
||||
indexList.push(gIndex)
|
||||
}
|
||||
})
|
||||
expandIndexList.value = indexList
|
||||
}
|
||||
|
||||
const toggleExpandOption = (item: WidgetDataSourceOptionSubObjVO, gIndex: number) => {
|
||||
if (expandIndexList.value.includes(gIndex)) {
|
||||
expandIndexList.value = expandIndexList.value.filter(num => num !== gIndex)
|
||||
} else {
|
||||
expandIndexList.value.push(gIndex)
|
||||
}
|
||||
}
|
||||
|
||||
const confirmBatchAddOptionEvent = () => {
|
||||
const { renderParams } = props
|
||||
const { widget } = renderParams
|
||||
const optList: WidgetDataSourceOptionSubObjVO[] = []
|
||||
const rowList = optionsContent.value.split('\n')
|
||||
let prevGroup: Required<WidgetDataSourceOptionObjVO> | null = null
|
||||
if (isSubOption) {
|
||||
rowList.forEach((str, index) => {
|
||||
const nextStr = rowList[index + 1]
|
||||
const value = str.trim()
|
||||
if (!value) {
|
||||
return
|
||||
}
|
||||
const item: WidgetDataSourceOptionSubObjVO = {
|
||||
value
|
||||
}
|
||||
if (prevGroup) {
|
||||
if (hasSubOption(str)) {
|
||||
prevGroup.options.push(item)
|
||||
return
|
||||
}
|
||||
prevGroup = null
|
||||
optList.push(item)
|
||||
} else {
|
||||
optList.push(item)
|
||||
}
|
||||
if (nextStr) {
|
||||
if (hasSubOption(nextStr)) {
|
||||
prevGroup = Object.assign(item, { options: [] })
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
rowList.forEach((str) => {
|
||||
optList.push({
|
||||
value: str.trim()
|
||||
})
|
||||
})
|
||||
}
|
||||
widget.options.options = optList
|
||||
expandAllOption()
|
||||
}
|
||||
|
||||
const openPopupEditEvent = () => {
|
||||
const { renderParams } = props
|
||||
const { widget } = renderParams
|
||||
|
||||
const contList: string[] = []
|
||||
widget.options.options?.forEach(group => {
|
||||
contList.push(group.value)
|
||||
group.options?.forEach(item => {
|
||||
contList.push(`\t${item.value}`)
|
||||
})
|
||||
})
|
||||
|
||||
optionsContent.value = contList.join('\n')
|
||||
|
||||
VxeUI.modal.open({
|
||||
title: `${widget.title} - ${VxeUI.getI18n('vxe.formDesign.widgetProp.dataSource.batchEditOption')}`,
|
||||
width: 500,
|
||||
height: '50vh ',
|
||||
resize: true,
|
||||
showFooter: true,
|
||||
showCancelButton: true,
|
||||
showConfirmButton: true,
|
||||
confirmButtonText: VxeUI.getI18n('vxe.formDesign.widgetProp.dataSource.buildOption'),
|
||||
onConfirm: confirmBatchAddOptionEvent,
|
||||
slots: {
|
||||
default () {
|
||||
return h('div', {
|
||||
class: 'vxe-form-design--widget-form-item-data-source-popup'
|
||||
}, [
|
||||
h(VxeUITipComponent, {
|
||||
status: 'primary',
|
||||
title: '',
|
||||
content: VxeUI.getI18n(`vxe.formDesign.widgetProp.dataSource.${isSubOption ? 'batchEditSubTip' : 'batchEditTip'}`)
|
||||
}),
|
||||
h(VxeUITextareaComponent, {
|
||||
resize: 'none',
|
||||
modelValue: optionsContent.value,
|
||||
'onUpdate:modelValue' (val: any) {
|
||||
optionsContent.value = val
|
||||
}
|
||||
})
|
||||
])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const renderOption = (item: WidgetDataSourceOptionSubObjVO, hasFirstLevel: boolean, isExpand: boolean, gIndex: number, hasSub: boolean, isFirst: boolean, isLast: boolean) => {
|
||||
return h('div', {
|
||||
class: ['vxe-form-design--widget-form-item-data-source-option', {
|
||||
'is--first': isFirst,
|
||||
'is--last': isLast
|
||||
}]
|
||||
}, [
|
||||
h('div', {
|
||||
class: 'vxe-form-design--widget-expand-btn'
|
||||
}, hasFirstLevel && hasSub
|
||||
? [
|
||||
h('i', {
|
||||
class: isExpand ? VxeUI.getIcon().FORM_DESIGN_WIDGET_OPTION_EXPAND_CLOSE : VxeUI.getIcon().FORM_DESIGN_WIDGET_OPTION_EXPAND_OPEN,
|
||||
onClick () {
|
||||
toggleExpandOption(item, gIndex)
|
||||
}
|
||||
})
|
||||
]
|
||||
: []),
|
||||
h('input', {
|
||||
class: 'vxe-default-input',
|
||||
value: item.value,
|
||||
onInput (evnt: InputEvent & { target: HTMLInputElement }) {
|
||||
item.value = evnt.target.value
|
||||
}
|
||||
}),
|
||||
h(VxeUIButtonComponent, {
|
||||
status: 'danger',
|
||||
mode: 'text',
|
||||
icon: VxeUI.getIcon().FORM_DESIGN_WIDGET_DELETE
|
||||
})
|
||||
])
|
||||
}
|
||||
|
||||
const renderOptions = () => {
|
||||
const { renderParams } = props
|
||||
const { widget } = renderParams
|
||||
const { options } = widget
|
||||
const groups = options.options
|
||||
const optVNs: VNode[] = []
|
||||
if (groups) {
|
||||
groups.forEach((group, gIndex) => {
|
||||
const { options } = group
|
||||
const isExpand = expandIndexList.value.includes(gIndex)
|
||||
if (options && options.length) {
|
||||
optVNs.push(renderOption(group, true, isExpand, gIndex, true, gIndex === 0, gIndex === groups.length - 1))
|
||||
if (isExpand) {
|
||||
optVNs.push(
|
||||
h('div', {
|
||||
class: 'vxe-form-design--widget-form-item-data-source-sub-option'
|
||||
}, options.map(item => renderOption(item, false, isExpand, 0, false, false, false)))
|
||||
)
|
||||
}
|
||||
} else {
|
||||
optVNs.push(renderOption(group, true, isExpand, gIndex, false, gIndex === 0, gIndex === groups.length - 1))
|
||||
}
|
||||
})
|
||||
}
|
||||
return optVNs
|
||||
}
|
||||
|
||||
watch(() => props.renderParams.widget, () => {
|
||||
expandAllOption()
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
expandAllOption()
|
||||
})
|
||||
|
||||
return {
|
||||
renderDataSourceFormItem () {
|
||||
return h(VxeUIFormItemComponent, {
|
||||
title: VxeUI.getI18n('vxe.formDesign.widgetProp.dataSource.name'),
|
||||
field: 'options'
|
||||
}, {
|
||||
default () {
|
||||
return [
|
||||
h('div', {}, [
|
||||
h(VxeUIButtonComponent, {
|
||||
status: 'primary',
|
||||
mode: 'text',
|
||||
content: VxeUI.getI18n('vxe.formDesign.widgetProp.dataSource.addOption'),
|
||||
onClick: addOptionEvent
|
||||
}),
|
||||
h(VxeUIButtonComponent, {
|
||||
status: 'primary',
|
||||
mode: 'text',
|
||||
content: VxeUI.getI18n('vxe.formDesign.widgetProp.dataSource.batchEditOption'),
|
||||
onClick: openPopupEditEvent
|
||||
})
|
||||
]),
|
||||
h('div', {
|
||||
class: 'vxe-form-design--widget-form-item-data-source'
|
||||
}, renderOptions())
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,244 @@
|
|||
import { h, resolveComponent, ComponentOptions } from 'vue'
|
||||
import XEUtils from 'xe-utils'
|
||||
|
||||
import type { VxeUIExport, VxeGlobalRendererHandles, FormItemContentRenderParams } from 'vxe-pc-ui'
|
||||
|
||||
/**
|
||||
* 表单 - 渲染器
|
||||
*/
|
||||
export function defineFormRender(VxeUI: VxeUIExport) {
|
||||
function isEmptyValue(cellValue: any) {
|
||||
return cellValue === null || cellValue === undefined || cellValue === ''
|
||||
}
|
||||
|
||||
function getOnName(type: string) {
|
||||
return 'on' + type.substring(0, 1).toLocaleUpperCase() + type.substring(1)
|
||||
}
|
||||
|
||||
function getModelProp(renderOpts: VxeGlobalRendererHandles.RenderOptions) {
|
||||
let prop = 'value'
|
||||
switch (renderOpts.name) {
|
||||
case 'ASwitch':
|
||||
prop = 'checked'
|
||||
break
|
||||
}
|
||||
return prop
|
||||
}
|
||||
|
||||
function getModelEvent(renderOpts: VxeGlobalRendererHandles.RenderOptions) {
|
||||
let type = 'update:value'
|
||||
switch (renderOpts.name) {
|
||||
case 'ASwitch':
|
||||
type = 'update:checked'
|
||||
break
|
||||
}
|
||||
return type
|
||||
}
|
||||
|
||||
function getChangeEvent(renderOpts: VxeGlobalRendererHandles.RenderOptions) {
|
||||
return 'change'
|
||||
}
|
||||
|
||||
function getItemProps(renderOpts: VxeGlobalRendererHandles.RenderOptions, params: FormItemContentRenderParams, value: any, defaultProps?: { [prop: string]: any }) {
|
||||
return XEUtils.assign({}, defaultProps, renderOpts.props, { [getModelProp(renderOpts)]: value })
|
||||
}
|
||||
|
||||
function formatText(cellValue: any) {
|
||||
return '' + (isEmptyValue(cellValue) ? '' : cellValue)
|
||||
}
|
||||
|
||||
function getOns(renderOpts: VxeGlobalRendererHandles.RenderOptions, params: VxeGlobalRendererHandles.RenderParams, inputFunc?: Function, changeFunc?: Function) {
|
||||
const { events } = renderOpts
|
||||
const modelEvent = getModelEvent(renderOpts)
|
||||
const changeEvent = getChangeEvent(renderOpts)
|
||||
const isSameEvent = changeEvent === modelEvent
|
||||
const ons: { [type: string]: Function } = {}
|
||||
XEUtils.objectEach(events, (func: Function, key: string) => {
|
||||
ons[getOnName(key)] = function (...args: any[]) {
|
||||
func(params, ...args)
|
||||
}
|
||||
})
|
||||
if (inputFunc) {
|
||||
ons[getOnName(modelEvent)] = function (targetEvnt: any) {
|
||||
inputFunc(targetEvnt)
|
||||
if (events && events[modelEvent]) {
|
||||
events[modelEvent](params, targetEvnt)
|
||||
}
|
||||
if (isSameEvent && changeFunc) {
|
||||
changeFunc(targetEvnt)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isSameEvent && changeFunc) {
|
||||
ons[getOnName(changeEvent)] = function (...args: any[]) {
|
||||
changeFunc(...args)
|
||||
if (events && events[changeEvent]) {
|
||||
events[changeEvent](params, ...args)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ons
|
||||
}
|
||||
|
||||
function getItemOns(renderOpts: VxeGlobalRendererHandles.RenderOptions, params: FormItemContentRenderParams) {
|
||||
const { $form, data, field } = params
|
||||
return getOns(renderOpts, params, (value: any) => {
|
||||
// 处理 model 值双向绑定
|
||||
XEUtils.set(data, field, value)
|
||||
}, () => {
|
||||
// 处理 change 事件相关逻辑
|
||||
$form.updateStatus(params)
|
||||
})
|
||||
}
|
||||
|
||||
function cellText(cellValue: any): string[] {
|
||||
return [formatText(cellValue)]
|
||||
}
|
||||
|
||||
function createFormItemRender(defaultProps?: { [key: string]: any }) {
|
||||
return function (renderOpts: VxeGlobalRendererHandles.RenderItemContentOptions & { name: string }, params: FormItemContentRenderParams) {
|
||||
const { data, field } = params
|
||||
const { name } = renderOpts
|
||||
const { attrs } = renderOpts
|
||||
const itemValue = XEUtils.get(data, field)
|
||||
return [
|
||||
h(resolveComponent(name), {
|
||||
...attrs,
|
||||
...getItemProps(renderOpts, params, itemValue, defaultProps),
|
||||
...getItemOns(renderOpts, params)
|
||||
})
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
function defaultButtonItemRender(renderOpts: VxeGlobalRendererHandles.RenderItemContentOptions, params: FormItemContentRenderParams) {
|
||||
const { attrs } = renderOpts
|
||||
const props = getItemProps(renderOpts, params, null)
|
||||
return [
|
||||
h(resolveComponent('a-button') as ComponentOptions, {
|
||||
...attrs,
|
||||
...props,
|
||||
...getItemOns(renderOpts, params)
|
||||
}, {
|
||||
default: () => cellText(renderOpts.content || props.content)
|
||||
})
|
||||
]
|
||||
}
|
||||
|
||||
function defaultButtonsItemRender(renderOpts: VxeGlobalRendererHandles.RenderItemContentOptions, params: FormItemContentRenderParams) {
|
||||
const { children } = renderOpts
|
||||
if (children) {
|
||||
return children.map((childRenderOpts: VxeGlobalRendererHandles.RenderItemContentOptions) => defaultButtonItemRender(childRenderOpts, params)[0])
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
function createFormItemRadioAndCheckboxRender() {
|
||||
return function (renderOpts: VxeGlobalRendererHandles.RenderItemContentOptions & { name: string }, params: FormItemContentRenderParams) {
|
||||
const { name, options = [], optionProps = {} } = renderOpts
|
||||
const { data, field } = params
|
||||
const { attrs } = renderOpts
|
||||
const labelProp = optionProps.label || 'label'
|
||||
const valueProp = optionProps.value || 'value'
|
||||
const itemValue = XEUtils.get(data, field)
|
||||
return [
|
||||
h(resolveComponent(`${name}Group`) as ComponentOptions, {
|
||||
...attrs,
|
||||
...getItemProps(renderOpts, params, itemValue),
|
||||
...getItemOns(renderOpts, params)
|
||||
}, {
|
||||
default: () => {
|
||||
return options.map((option, oIndex) => {
|
||||
return h(resolveComponent(name) as ComponentOptions, {
|
||||
key: oIndex,
|
||||
value: option[valueProp],
|
||||
disabled: option.disabled
|
||||
}, {
|
||||
default: () => cellText(option[labelProp])
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
VxeUI.renderer.mixin({
|
||||
AAutoComplete: {
|
||||
renderFormItemContent: createFormItemRender()
|
||||
},
|
||||
AInput: {
|
||||
renderFormItemContent: createFormItemRender()
|
||||
},
|
||||
AInputNumber: {
|
||||
renderFormItemContent: createFormItemRender()
|
||||
},
|
||||
ASelect: {
|
||||
renderFormItemContent(renderOpts, params) {
|
||||
const { options = [], optionGroups } = renderOpts
|
||||
const { data, field } = params
|
||||
const { attrs } = renderOpts
|
||||
const itemValue = XEUtils.get(data, field)
|
||||
const props = getItemProps(renderOpts, params, itemValue)
|
||||
const ons = getItemOns(renderOpts, params)
|
||||
if (optionGroups) {
|
||||
return [
|
||||
h(resolveComponent('n-select') as ComponentOptions, {
|
||||
...attrs,
|
||||
...props,
|
||||
options: optionGroups,
|
||||
...ons
|
||||
})
|
||||
]
|
||||
}
|
||||
return [
|
||||
h(resolveComponent('n-select') as ComponentOptions, {
|
||||
...attrs,
|
||||
...props,
|
||||
options: props.options || options,
|
||||
...ons
|
||||
})
|
||||
]
|
||||
}
|
||||
},
|
||||
ACascader: {
|
||||
renderFormItemContent: createFormItemRender()
|
||||
},
|
||||
ADatePicker: {
|
||||
renderFormItemContent: createFormItemRender()
|
||||
},
|
||||
AMonthPicker: {
|
||||
renderFormItemContent: createFormItemRender()
|
||||
},
|
||||
ARangePicker: {
|
||||
renderFormItemContent: createFormItemRender()
|
||||
},
|
||||
AWeekPicker: {
|
||||
renderFormItemContent: createFormItemRender()
|
||||
},
|
||||
ATimePicker: {
|
||||
renderFormItemContent: createFormItemRender()
|
||||
},
|
||||
ATreeSelect: {
|
||||
renderFormItemContent: createFormItemRender()
|
||||
},
|
||||
ARate: {
|
||||
renderFormItemContent: createFormItemRender()
|
||||
},
|
||||
ASwitch: {
|
||||
renderFormItemContent: createFormItemRender()
|
||||
},
|
||||
ARadio: {
|
||||
renderFormItemContent: createFormItemRadioAndCheckboxRender()
|
||||
},
|
||||
ACheckbox: {
|
||||
renderFormItemContent: createFormItemRadioAndCheckboxRender()
|
||||
},
|
||||
AButton: {
|
||||
renderFormItemContent: defaultButtonItemRender
|
||||
},
|
||||
AButtons: {
|
||||
renderFormItemContent: defaultButtonsItemRender
|
||||
}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
import { defineTableRender } from './table'
|
||||
import { defineFormRender } from './form'
|
||||
import { defineFormDesignRender } from './form-design'
|
||||
|
||||
import type { VxeUIPluginObject, VxeGlobalInterceptorHandles } from 'vxe-pc-ui'
|
||||
|
||||
/**
|
||||
* 检查触发源是否属于目标节点
|
||||
*/
|
||||
function getEventTargetNode(evnt: any, container: HTMLElement, className: string) {
|
||||
let targetElem
|
||||
let target = evnt.target
|
||||
while (target && target.nodeType && target !== document) {
|
||||
if (className && target.className && target.className.split && target.className.split(' ').indexOf(className) > -1) {
|
||||
targetElem = target
|
||||
} else if (target === container) {
|
||||
return { flag: className ? !!targetElem : true, container, targetElem: targetElem }
|
||||
}
|
||||
target = target.parentNode
|
||||
}
|
||||
return { flag: false }
|
||||
}
|
||||
|
||||
/**
|
||||
* 事件兼容性处理
|
||||
*/
|
||||
function handleClearEvent(params: VxeGlobalInterceptorHandles.InterceptorClearFilterParams | VxeGlobalInterceptorHandles.InterceptorClearEditParams | VxeGlobalInterceptorHandles.InterceptorClearAreasParams) {
|
||||
const { $event } = params
|
||||
const bodyElem = document.body
|
||||
if (
|
||||
// 下拉框
|
||||
getEventTargetNode($event, bodyElem, 'ant-select-dropdown').flag ||
|
||||
// 级联
|
||||
getEventTargetNode($event, bodyElem, 'ant-cascader-menus').flag ||
|
||||
// 日期
|
||||
getEventTargetNode($event, bodyElem, 'ant-picker-dropdown').flag ||
|
||||
getEventTargetNode($event, bodyElem, 'ant-calendar-picker-container').flag ||
|
||||
// 时间选择
|
||||
getEventTargetNode($event, bodyElem, 'ant-time-picker-panel').flag
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export const VxeUIPluginRenderNaive: VxeUIPluginObject = {
|
||||
install(VxeUI, options?: {
|
||||
Naive?: any
|
||||
}) {
|
||||
// 检查版本
|
||||
if (!/^(4)\./.test(VxeUI.uiVersion)) {
|
||||
console.error('[plugin-render-naive 4.x] Version 4.x is required')
|
||||
}
|
||||
|
||||
defineTableRender(VxeUI)
|
||||
defineFormRender(VxeUI)
|
||||
defineFormDesignRender(VxeUI)
|
||||
|
||||
VxeUI.interceptor.add('event.clearFilter', handleClearEvent)
|
||||
VxeUI.interceptor.add('event.clearEdit', handleClearEvent)
|
||||
VxeUI.interceptor.add('event.clearAreas', handleClearEvent)
|
||||
|
||||
// 兼容老版本
|
||||
VxeUI.interceptor.add('event.clearActived', handleClearEvent)
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof window !== 'undefined' && window.VxeUI && window.VxeUI.use) {
|
||||
window.VxeUI.use(VxeUIPluginRenderNaive)
|
||||
}
|
||||
|
||||
export default VxeUIPluginRenderNaive
|
|
@ -0,0 +1,547 @@
|
|||
import { h, resolveComponent, ComponentOptions } from 'vue'
|
||||
import XEUtils from 'xe-utils'
|
||||
|
||||
import type { VxeUIExport, VxeTableDefines, VxeColumnPropTypes, VxeGlobalRendererHandles } from 'vxe-pc-ui'
|
||||
|
||||
/**
|
||||
* 表格 - 渲染器
|
||||
*/
|
||||
export function defineTableRender(VxeUI: VxeUIExport) {
|
||||
function isEmptyValue(cellValue: any) {
|
||||
return cellValue === null || cellValue === undefined || cellValue === ''
|
||||
}
|
||||
|
||||
function getOnName(type: string) {
|
||||
return 'on' + type.substring(0, 1).toLocaleUpperCase() + type.substring(1)
|
||||
}
|
||||
|
||||
function getModelProp(renderOpts: VxeGlobalRendererHandles.RenderOptions) {
|
||||
let prop = 'value'
|
||||
switch (renderOpts.name) {
|
||||
case 'ASwitch':
|
||||
prop = 'checked'
|
||||
break
|
||||
}
|
||||
return prop
|
||||
}
|
||||
|
||||
function getModelEvent(renderOpts: VxeGlobalRendererHandles.RenderOptions) {
|
||||
let type = 'update:value'
|
||||
switch (renderOpts.name) {
|
||||
case 'ASwitch':
|
||||
type = 'update:checked'
|
||||
break
|
||||
}
|
||||
return type
|
||||
}
|
||||
|
||||
function dateFormatToVxeFormat(format: string) {
|
||||
if (format) {
|
||||
return `${format}`.replace('YYYY', 'yyyy').replace('DD', 'dd')
|
||||
}
|
||||
return format
|
||||
}
|
||||
|
||||
function getChangeEvent(renderOpts: VxeGlobalRendererHandles.RenderOptions) {
|
||||
return 'change'
|
||||
}
|
||||
|
||||
function getCellEditFilterProps(renderOpts: any, params: VxeGlobalRendererHandles.RenderEditParams | VxeGlobalRendererHandles.RenderFilterParams, value: any, defaultProps?: { [prop: string]: any }) {
|
||||
return XEUtils.assign({}, defaultProps, renderOpts.props, { [getModelProp(renderOpts)]: value })
|
||||
}
|
||||
|
||||
function formatText(cellValue: any) {
|
||||
return '' + (isEmptyValue(cellValue) ? '' : cellValue)
|
||||
}
|
||||
|
||||
function getCellLabelVNs(renderOpts: VxeColumnPropTypes.EditRender, params: VxeGlobalRendererHandles.RenderCellParams, cellLabel: any) {
|
||||
const { placeholder } = renderOpts
|
||||
return [
|
||||
h('span', {
|
||||
class: 'vxe-cell--label'
|
||||
}, placeholder && isEmptyValue(cellLabel)
|
||||
? [
|
||||
h('span', {
|
||||
class: 'vxe-cell--placeholder'
|
||||
}, formatText(placeholder))
|
||||
]
|
||||
: formatText(cellLabel))
|
||||
]
|
||||
}
|
||||
|
||||
function getOns(renderOpts: VxeGlobalRendererHandles.RenderOptions, params: VxeGlobalRendererHandles.RenderParams, inputFunc?: Function, changeFunc?: Function) {
|
||||
const { events } = renderOpts
|
||||
const modelEvent = getModelEvent(renderOpts)
|
||||
const changeEvent = getChangeEvent(renderOpts)
|
||||
const isSameEvent = changeEvent === modelEvent
|
||||
const ons: { [type: string]: Function } = {}
|
||||
XEUtils.objectEach(events, (func: Function, key: string) => {
|
||||
ons[getOnName(key)] = function (...args: any[]) {
|
||||
func(params, ...args)
|
||||
}
|
||||
})
|
||||
if (inputFunc) {
|
||||
ons[getOnName(modelEvent)] = function (targetEvnt: any) {
|
||||
inputFunc(targetEvnt)
|
||||
if (events && events[modelEvent]) {
|
||||
events[modelEvent](params, targetEvnt)
|
||||
}
|
||||
if (isSameEvent && changeFunc) {
|
||||
changeFunc(targetEvnt)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isSameEvent && changeFunc) {
|
||||
ons[getOnName(changeEvent)] = function (...args: any[]) {
|
||||
changeFunc(...args)
|
||||
if (events && events[changeEvent]) {
|
||||
events[changeEvent](params, ...args)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ons
|
||||
}
|
||||
|
||||
function getEditOns(renderOpts: VxeGlobalRendererHandles.RenderOptions, params: VxeGlobalRendererHandles.RenderEditParams) {
|
||||
const { $table, row, column } = params
|
||||
return getOns(renderOpts, params, (value: any) => {
|
||||
// 处理 model 值双向绑定
|
||||
XEUtils.set(row, column.field, value)
|
||||
}, () => {
|
||||
// 处理 change 事件相关逻辑
|
||||
$table.updateStatus(params)
|
||||
})
|
||||
}
|
||||
|
||||
function getFilterOns(renderOpts: any, params: VxeGlobalRendererHandles.RenderFilterParams, option: VxeTableDefines.FilterOption, changeFunc: Function) {
|
||||
return getOns(renderOpts, params, (value: any) => {
|
||||
// 处理 model 值双向绑定
|
||||
option.data = value
|
||||
}, changeFunc)
|
||||
}
|
||||
|
||||
function matchCascaderData(index: number, list: any[], values: any[], labels: any[]) {
|
||||
const val = values[index]
|
||||
if (list && values.length > index) {
|
||||
XEUtils.each(list, (item) => {
|
||||
if (item.value === val) {
|
||||
labels.push(item.label)
|
||||
matchCascaderData(++index, item.children, values, labels)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function formatDatePicker(defaultFormat?: string) {
|
||||
return function (renderOpts: VxeColumnPropTypes.EditRender, params: VxeGlobalRendererHandles.RenderCellParams) {
|
||||
return getCellLabelVNs(renderOpts, params, getDatePickerCellValue(renderOpts, params, defaultFormat))
|
||||
}
|
||||
}
|
||||
|
||||
function formatTimePicker(defaultFormat?: string) {
|
||||
return function (renderOpts: VxeColumnPropTypes.EditRender, params: VxeGlobalRendererHandles.RenderCellParams) {
|
||||
const { props = {} } = renderOpts
|
||||
const { row, column } = params
|
||||
let cellValue = XEUtils.get(row, column.field)
|
||||
try {
|
||||
if (cellValue) {
|
||||
if (!XEUtils.isString(cellValue)) {
|
||||
cellValue = cellValue.format ? cellValue.format(props.format || props.valueFormat || defaultFormat) : XEUtils.toDateString(cellValue, dateFormatToVxeFormat(props.format || props.valueFormat || defaultFormat))
|
||||
}
|
||||
}
|
||||
} catch (e) { }
|
||||
return getCellLabelVNs(renderOpts, params, cellValue)
|
||||
}
|
||||
}
|
||||
|
||||
function getSelectCellValue(renderOpts: VxeColumnPropTypes.EditRender, params: VxeGlobalRendererHandles.RenderCellParams) {
|
||||
const { options = [], optionGroups, props = {}, optionProps = {}, optionGroupProps = {} } = renderOpts
|
||||
const { row, column } = params
|
||||
const labelProp = optionProps.label || 'label'
|
||||
const valueProp = optionProps.value || 'value'
|
||||
const groupOptions = optionGroupProps.options || 'options'
|
||||
const cellValue = XEUtils.get(row, column.field)
|
||||
if (!isEmptyValue(cellValue)) {
|
||||
return XEUtils.map(props.mode === 'multiple' ? cellValue : [cellValue], optionGroups
|
||||
? (value) => {
|
||||
let selectItem
|
||||
for (let index = 0; index < optionGroups.length; index++) {
|
||||
selectItem = XEUtils.find(optionGroups[index][groupOptions], (item) => item[valueProp] === value)
|
||||
if (selectItem) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return selectItem ? selectItem[labelProp] : value
|
||||
}
|
||||
: (value) => {
|
||||
const selectItem = XEUtils.find(options, (item) => item[valueProp] === value)
|
||||
return selectItem ? selectItem[labelProp] : value
|
||||
}).join(', ')
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
function getCascaderCellValue(renderOpts: VxeGlobalRendererHandles.RenderOptions, params: VxeGlobalRendererHandles.RenderCellParams | VxeGlobalRendererHandles.ExportMethodParams) {
|
||||
const { props = {} } = renderOpts
|
||||
const { row, column } = params
|
||||
const cellValue = XEUtils.get(row, column.field)
|
||||
const values = cellValue || []
|
||||
const labels: Array<any> = []
|
||||
matchCascaderData(0, props.options, values, labels)
|
||||
return (props.showAllLevels === false ? labels.slice(labels.length - 1, labels.length) : labels).join(` ${props.separator || '/'} `)
|
||||
}
|
||||
|
||||
function getRangePickerCellValue(renderOpts: VxeColumnPropTypes.EditRender, params: VxeGlobalRendererHandles.RenderCellParams | VxeGlobalRendererHandles.RenderEditParams) {
|
||||
const { props = {} } = renderOpts
|
||||
const { row, column } = params
|
||||
let cellValue = XEUtils.get(row, column.field)
|
||||
if (cellValue) {
|
||||
cellValue = XEUtils.map(cellValue, (date: any) => {
|
||||
return date && date.format ? date.format(props.format || 'YYYY-MM-DD') : XEUtils.toDateString(date, dateFormatToVxeFormat(props.format || 'YYYY-MM-DD'))
|
||||
}).join(' ~ ')
|
||||
}
|
||||
return cellValue
|
||||
}
|
||||
|
||||
function getTreeSelectCellValue(renderOpts: VxeGlobalRendererHandles.RenderOptions, params: VxeGlobalRendererHandles.RenderCellParams | VxeGlobalRendererHandles.RenderEditParams) {
|
||||
const { props = {} } = renderOpts
|
||||
const { treeData, treeCheckable } = props
|
||||
const { row, column } = params
|
||||
const cellValue = XEUtils.get(row, column.field)
|
||||
if (!isEmptyValue(cellValue)) {
|
||||
return XEUtils.map(treeCheckable ? cellValue : [cellValue], (value) => {
|
||||
const matchObj = XEUtils.findTree(treeData, (item: any) => item.value === value, { children: 'children' })
|
||||
return matchObj ? matchObj.item.title : value
|
||||
}).join(', ')
|
||||
}
|
||||
return cellValue
|
||||
}
|
||||
|
||||
function getDatePickerCellValue(renderOpts: VxeGlobalRendererHandles.RenderOptions, params: VxeGlobalRendererHandles.RenderCellParams | VxeGlobalRendererHandles.ExportMethodParams, defaultFormat?: string) {
|
||||
const { props = {} } = renderOpts
|
||||
const { row, column } = params
|
||||
let cellValue = XEUtils.get(row, column.field)
|
||||
try {
|
||||
if (cellValue) {
|
||||
if (!defaultFormat) {
|
||||
if (renderOpts.name === 'ADatePicker') {
|
||||
switch (props.picker) {
|
||||
case 'week':
|
||||
defaultFormat = 'YYYY-WW周'
|
||||
break
|
||||
case 'month':
|
||||
defaultFormat = 'YYYY-MM'
|
||||
break
|
||||
case 'year':
|
||||
defaultFormat = 'YYYY'
|
||||
break
|
||||
default:
|
||||
defaultFormat = props.showTime ? 'YYYY-MM-DD HH:mm:ss' : 'YYYY-MM-DD'
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
cellValue = cellValue.format ? cellValue.format(props.format || defaultFormat) : XEUtils.toDateString(cellValue, dateFormatToVxeFormat(props.format || defaultFormat))
|
||||
}
|
||||
} catch (e) { }
|
||||
return cellValue
|
||||
}
|
||||
|
||||
function createEditRender(defaultProps?: { [key: string]: any }) {
|
||||
return function (renderOpts: VxeColumnPropTypes.EditRender & { name: string }, params: VxeGlobalRendererHandles.RenderEditParams) {
|
||||
debugger
|
||||
const { row, column } = params
|
||||
const { name, attrs } = renderOpts
|
||||
console.log(params)
|
||||
console.log(renderOpts)
|
||||
|
||||
const cellValue = XEUtils.get(row, column.field)
|
||||
return [
|
||||
h(resolveComponent(name), {
|
||||
...attrs,
|
||||
...getCellEditFilterProps(renderOpts, params, cellValue, defaultProps),
|
||||
...getEditOns(renderOpts, params)
|
||||
})
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
function defaultButtonEditRender(renderOpts: VxeColumnPropTypes.EditRender, params: VxeGlobalRendererHandles.RenderEditParams) {
|
||||
const { attrs } = renderOpts
|
||||
return [
|
||||
h(resolveComponent('n-button'), {
|
||||
...attrs,
|
||||
...getCellEditFilterProps(renderOpts, params, null),
|
||||
...getOns(renderOpts, params)
|
||||
}, cellText(renderOpts.content))
|
||||
]
|
||||
}
|
||||
|
||||
function defaultButtonsEditRender(renderOpts: VxeColumnPropTypes.EditRender, params: VxeGlobalRendererHandles.RenderEditParams) {
|
||||
const { children } = renderOpts
|
||||
if (children) {
|
||||
return children.map((childRenderOpts: VxeColumnPropTypes.EditRender) => defaultButtonEditRender(childRenderOpts, params)[0])
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
function createFilterRender(defaultProps?: { [key: string]: any }) {
|
||||
return function (renderOpts: VxeColumnPropTypes.FilterRender & { name: string }, params: VxeGlobalRendererHandles.RenderFilterParams) {
|
||||
const { column } = params
|
||||
const { name, attrs } = renderOpts
|
||||
return [
|
||||
h('div', {
|
||||
class: 'vxe-table--filter-naive-wrapper'
|
||||
}, column.filters.map((option, oIndex) => {
|
||||
const optionValue = option.data
|
||||
return h(resolveComponent(name), {
|
||||
key: oIndex,
|
||||
...attrs,
|
||||
...getCellEditFilterProps(renderOpts, params, optionValue, defaultProps),
|
||||
...getFilterOns(renderOpts, params, option, () => {
|
||||
// 处理 change 事件相关逻辑
|
||||
handleConfirmFilter(params, !!option.data, option)
|
||||
})
|
||||
})
|
||||
}))
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
function handleConfirmFilter(params: VxeGlobalRendererHandles.RenderFilterParams, checked: boolean, option: VxeTableDefines.FilterOption) {
|
||||
const { $panel } = params
|
||||
$panel.changeOption(null, checked, option)
|
||||
}
|
||||
|
||||
/**
|
||||
* 模糊匹配
|
||||
* @param params
|
||||
*/
|
||||
function defaultFuzzyFilterMethod(params: VxeGlobalRendererHandles.FilterMethodParams) {
|
||||
const { option, row, column } = params
|
||||
const { data } = option
|
||||
const cellValue = XEUtils.get(row, column.field)
|
||||
return XEUtils.toValueString(cellValue).indexOf(data) > -1
|
||||
}
|
||||
|
||||
/**
|
||||
* 精确匹配
|
||||
* @param params
|
||||
*/
|
||||
function defaultExactFilterMethod(params: VxeGlobalRendererHandles.FilterMethodParams) {
|
||||
const { option, row, column } = params
|
||||
const { data } = option
|
||||
const cellValue = XEUtils.get(row, column.field)
|
||||
/* eslint-disable eqeqeq */
|
||||
return cellValue === data
|
||||
}
|
||||
|
||||
function cellText(cellValue: any): string[] {
|
||||
return [formatText(cellValue)]
|
||||
}
|
||||
|
||||
function createDatePickerExportMethod(defaultFormat?: string) {
|
||||
return function (params: VxeGlobalRendererHandles.ExportMethodParams) {
|
||||
const { row, column, options } = params
|
||||
return options && options.original ? XEUtils.get(row, column.field) : getDatePickerCellValue(column.editRender || column.cellRender, params, defaultFormat)
|
||||
}
|
||||
}
|
||||
|
||||
function createExportMethod(getExportCellValue: Function) {
|
||||
return function (params: VxeGlobalRendererHandles.ExportMethodParams) {
|
||||
const { row, column, options } = params
|
||||
return options && options.original ? XEUtils.get(row, column.field) : getExportCellValue(column.editRender || column.cellRender, params)
|
||||
}
|
||||
}
|
||||
|
||||
VxeUI.renderer.mixin({
|
||||
AAutoComplete: {
|
||||
tableAutoFocus: 'input',
|
||||
renderTableDefault: createEditRender(),
|
||||
renderTableEdit: createEditRender(),
|
||||
renderTableFilter: createFilterRender(),
|
||||
tableFilterDefaultMethod: defaultExactFilterMethod
|
||||
},
|
||||
NInput: {
|
||||
tableAutoFocus: 'input',
|
||||
renderTableDefault: createEditRender(),
|
||||
renderTableEdit: createEditRender(),
|
||||
renderTableFilter: createFilterRender(),
|
||||
tableFilterDefaultMethod: defaultFuzzyFilterMethod
|
||||
},
|
||||
NInputNumber: {
|
||||
tableAutoFocus: 'input',
|
||||
renderTableDefault: createEditRender(),
|
||||
renderTableEdit: createEditRender(),
|
||||
renderTableFilter: createFilterRender(),
|
||||
tableFilterDefaultMethod: defaultFuzzyFilterMethod
|
||||
},
|
||||
NSelect: {
|
||||
renderTableEdit(renderOpts, params) {
|
||||
const { options, optionGroups } = renderOpts
|
||||
const { row, column } = params
|
||||
const { attrs } = renderOpts
|
||||
const cellValue = XEUtils.get(row, column.field)
|
||||
const props = getCellEditFilterProps(renderOpts, params, cellValue)
|
||||
const ons = getEditOns(renderOpts, params)
|
||||
if (optionGroups) {
|
||||
return [
|
||||
h(resolveComponent('n-select') as ComponentOptions, {
|
||||
...props,
|
||||
...attrs,
|
||||
options: optionGroups,
|
||||
...ons
|
||||
})
|
||||
]
|
||||
}
|
||||
return [
|
||||
h(resolveComponent('n-select') as ComponentOptions, {
|
||||
...props,
|
||||
...attrs,
|
||||
options: props.options || options,
|
||||
...ons
|
||||
})
|
||||
]
|
||||
},
|
||||
renderTableCell(renderOpts, params) {
|
||||
return getCellLabelVNs(renderOpts, params, getSelectCellValue(renderOpts, params))
|
||||
},
|
||||
renderTableFilter(renderOpts, params) {
|
||||
debugger
|
||||
const { options = [], optionGroups, optionGroupProps = {} } = renderOpts
|
||||
const groupOptions = optionGroupProps.options || 'options'
|
||||
const { column } = params
|
||||
const { attrs } = renderOpts
|
||||
return [
|
||||
h('div', {
|
||||
class: 'vxe-table--filter-naive-wrapper'
|
||||
}, optionGroups
|
||||
? column.filters.map((option, oIndex) => {
|
||||
const optionValue = option.data
|
||||
const props = getCellEditFilterProps(renderOpts, params, optionValue)
|
||||
return h(resolveComponent('n-select') as ComponentOptions, {
|
||||
key: oIndex,
|
||||
...attrs,
|
||||
...props,
|
||||
options: groupOptions,
|
||||
...getFilterOns(renderOpts, params, option, () => {
|
||||
// 处理 change 事件相关逻辑
|
||||
handleConfirmFilter(params, props.mode === 'multiple' ? (option.data && option.data.length > 0) : !XEUtils.eqNull(option.data), option)
|
||||
})
|
||||
})
|
||||
})
|
||||
: column.filters.map((option, oIndex) => {
|
||||
const optionValue = option.data
|
||||
const props = getCellEditFilterProps(renderOpts, params, optionValue)
|
||||
return h(resolveComponent('n-select') as ComponentOptions, {
|
||||
key: oIndex,
|
||||
...attrs,
|
||||
...props,
|
||||
options: props.options || options,
|
||||
...getFilterOns(renderOpts, params, option, () => {
|
||||
// 处理 change 事件相关逻辑
|
||||
handleConfirmFilter(params, props.mode === 'multiple' ? (option.data && option.data.length > 0) : !XEUtils.eqNull(option.data), option)
|
||||
})
|
||||
})
|
||||
}))
|
||||
]
|
||||
},
|
||||
tableFilterDefaultMethod(params) {
|
||||
const { option, row, column } = params
|
||||
const { data } = option
|
||||
const { field, filterRender: renderOpts } = column
|
||||
const { props = {} } = renderOpts
|
||||
const cellValue = XEUtils.get(row, field)
|
||||
if (props.mode === 'multiple') {
|
||||
if (XEUtils.isArray(cellValue)) {
|
||||
return XEUtils.includeArrays(cellValue, data)
|
||||
}
|
||||
return data.indexOf(cellValue) > -1
|
||||
}
|
||||
/* eslint-disable eqeqeq */
|
||||
return cellValue == data
|
||||
},
|
||||
tableExportMethod: createExportMethod(getSelectCellValue)
|
||||
},
|
||||
ACascader: {
|
||||
renderTableEdit: createEditRender(),
|
||||
renderTableCell(renderOpts, params) {
|
||||
return getCellLabelVNs(renderOpts, params, getCascaderCellValue(renderOpts, params))
|
||||
},
|
||||
tableExportMethod: createExportMethod(getCascaderCellValue)
|
||||
},
|
||||
ADatePicker: {
|
||||
renderTableEdit: createEditRender(),
|
||||
renderTableCell: formatDatePicker(),
|
||||
tableExportMethod: createDatePickerExportMethod()
|
||||
},
|
||||
AMonthPicker: {
|
||||
renderTableEdit: createEditRender(),
|
||||
renderTableCell: formatDatePicker('YYYY-MM'),
|
||||
tableExportMethod: createDatePickerExportMethod('YYYY-MM')
|
||||
},
|
||||
ARangePicker: {
|
||||
renderTableEdit: createEditRender(),
|
||||
renderTableCell(renderOpts, params) {
|
||||
return getCellLabelVNs(renderOpts, params, getRangePickerCellValue(renderOpts, params))
|
||||
},
|
||||
tableExportMethod: createExportMethod(getRangePickerCellValue)
|
||||
},
|
||||
AWeekPicker: {
|
||||
renderTableEdit: createEditRender(),
|
||||
renderTableCell: formatDatePicker('YYYY-WW周'),
|
||||
tableExportMethod: createDatePickerExportMethod('YYYY-WW周')
|
||||
},
|
||||
ATimePicker: {
|
||||
renderTableEdit: createEditRender(),
|
||||
renderTableCell: formatTimePicker('HH:mm:ss'),
|
||||
tableExportMethod: createDatePickerExportMethod('HH:mm:ss')
|
||||
},
|
||||
ATreeSelect: {
|
||||
renderTableEdit: createEditRender(),
|
||||
renderTableCell(renderOpts, params) {
|
||||
return getCellLabelVNs(renderOpts, params, getTreeSelectCellValue(renderOpts, params))
|
||||
},
|
||||
tableExportMethod: createExportMethod(getTreeSelectCellValue)
|
||||
},
|
||||
ARate: {
|
||||
renderTableDefault: createEditRender(),
|
||||
renderTableEdit: createEditRender(),
|
||||
renderTableFilter: createFilterRender(),
|
||||
tableFilterDefaultMethod: defaultExactFilterMethod
|
||||
},
|
||||
ASwitch: {
|
||||
renderTableDefault: createEditRender(),
|
||||
renderTableEdit: createEditRender(),
|
||||
renderTableFilter(renderOpts, params) {
|
||||
const { column } = params
|
||||
const { name, attrs } = renderOpts
|
||||
return [
|
||||
h('div', {
|
||||
class: 'vxe-table--filter-naive-wrapper'
|
||||
}, column.filters.map((option, oIndex) => {
|
||||
const optionValue = option.data
|
||||
return h(name as string, {
|
||||
key: oIndex,
|
||||
...attrs,
|
||||
...getCellEditFilterProps(renderOpts, params, optionValue),
|
||||
...getFilterOns(renderOpts, params, option, () => {
|
||||
// 处理 change 事件相关逻辑
|
||||
handleConfirmFilter(params, XEUtils.isBoolean(option.data), option)
|
||||
})
|
||||
})
|
||||
}))
|
||||
]
|
||||
},
|
||||
tableFilterDefaultMethod: defaultExactFilterMethod
|
||||
},
|
||||
AButton: {
|
||||
renderTableEdit: defaultButtonEditRender,
|
||||
renderTableDefault: defaultButtonEditRender
|
||||
},
|
||||
AButtons: {
|
||||
renderTableEdit: defaultButtonsEditRender,
|
||||
renderTableDefault: defaultButtonsEditRender
|
||||
}
|
||||
})
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue