Commit 162b9d70652a1a8ffcb0f7dcb3f89828eb83d442
0 parents
收银台前端工程
Showing
95 changed files
with
4645 additions
and
0 deletions
Too many changes to show.
To preserve performance only 95 of 1357 files are displayed.
.browserslistrc
0 → 100644
.changeset/README.md
0 → 100644
| 1 | +++ a/.changeset/README.md | ||
| 1 | +# Changesets | ||
| 2 | + | ||
| 3 | +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works with multi-package repos, or single-package repos to help you version and publish your code. You can find the full documentation for it [in our repository](https://github.com/changesets/changesets) | ||
| 4 | + | ||
| 5 | +We have a quick list of common questions to get you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) |
.changeset/config.json
0 → 100644
| 1 | +++ a/.changeset/config.json | ||
| 1 | +{ | ||
| 2 | + "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", | ||
| 3 | + "changelog": [ | ||
| 4 | + "@changesets/changelog-github", | ||
| 5 | + { "repo": "vbenjs/vue-vben-admin" } | ||
| 6 | + ], | ||
| 7 | + "commit": false, | ||
| 8 | + "fixed": [["@vben-core/*", "@vben/*"]], | ||
| 9 | + "snapshot": { | ||
| 10 | + "prereleaseTemplate": "{tag}-{datetime}" | ||
| 11 | + }, | ||
| 12 | + "privatePackages": { "version": true, "tag": true }, | ||
| 13 | + "linked": [], | ||
| 14 | + "access": "public", | ||
| 15 | + "baseBranch": "main", | ||
| 16 | + "updateInternalDependencies": "patch", | ||
| 17 | + "ignore": [] | ||
| 18 | +} |
.commitlintrc.js
0 → 100644
.dockerignore
0 → 100644
.editorconfig
0 → 100644
| 1 | +++ a/.editorconfig | ||
| 1 | +root = true | ||
| 2 | + | ||
| 3 | +[*] | ||
| 4 | +charset=utf-8 | ||
| 5 | +end_of_line=lf | ||
| 6 | +insert_final_newline=true | ||
| 7 | +indent_style=space | ||
| 8 | +indent_size=2 | ||
| 9 | +max_line_length = 100 | ||
| 10 | +trim_trailing_whitespace = true | ||
| 11 | +quote_type = single | ||
| 12 | + | ||
| 13 | +[*.{yml,yaml,json}] | ||
| 14 | +indent_style = space | ||
| 15 | +indent_size = 2 | ||
| 16 | + | ||
| 17 | +[*.md] | ||
| 18 | +trim_trailing_whitespace = false |
.gitattributes
0 → 100644
| 1 | +++ a/.gitattributes | ||
| 1 | +# https://docs.github.com/cn/get-started/getting-started-with-git/configuring-git-to-handle-line-endings | ||
| 2 | + | ||
| 3 | +# Automatically normalize line endings (to LF) for all text-based files. | ||
| 4 | +* text=auto eol=lf | ||
| 5 | + | ||
| 6 | +# Declare files that will always have CRLF line endings on checkout. | ||
| 7 | +*.{cmd,[cC][mM][dD]} text eol=crlf | ||
| 8 | +*.{bat,[bB][aA][tT]} text eol=crlf | ||
| 9 | + | ||
| 10 | +# Denote all files that are truly binary and should not be modified. | ||
| 11 | +*.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary | ||
| 0 | \ No newline at end of file | 12 | \ No newline at end of file |
.gitconfig
0 → 100644
.gitignore
0 → 100644
| 1 | +++ a/.gitignore | ||
| 1 | +node_modules | ||
| 2 | +.DS_Store | ||
| 3 | +dist | ||
| 4 | +dist-ssr | ||
| 5 | +dist.zip | ||
| 6 | +dist.tar | ||
| 7 | +dist.war | ||
| 8 | +.nitro | ||
| 9 | +.output | ||
| 10 | +*-dist.zip | ||
| 11 | +*-dist.tar | ||
| 12 | +*-dist.war | ||
| 13 | +coverage | ||
| 14 | +*.local | ||
| 15 | +**/.vitepress/cache | ||
| 16 | +.cache | ||
| 17 | +.turbo | ||
| 18 | +.temp | ||
| 19 | +dev-dist | ||
| 20 | +.stylelintcache | ||
| 21 | +yarn.lock | ||
| 22 | +package-lock.json | ||
| 23 | +.VSCodeCounter | ||
| 24 | +**/backend-mock/data | ||
| 25 | + | ||
| 26 | +# local env files | ||
| 27 | +.env.local | ||
| 28 | +.env.*.local | ||
| 29 | +.eslintcache | ||
| 30 | + | ||
| 31 | +logs | ||
| 32 | +*.log | ||
| 33 | +npm-debug.log* | ||
| 34 | +yarn-debug.log* | ||
| 35 | +yarn-error.log* | ||
| 36 | +pnpm-debug.log* | ||
| 37 | +lerna-debug.log* | ||
| 38 | +vite.config.mts.* | ||
| 39 | +vite.config.mjs.* | ||
| 40 | +vite.config.js.* | ||
| 41 | +vite.config.ts.* | ||
| 42 | + | ||
| 43 | +# Editor directories and files | ||
| 44 | +.idea | ||
| 45 | +# .vscode | ||
| 46 | +*.suo | ||
| 47 | +*.ntvs* | ||
| 48 | +*.njsproj | ||
| 49 | +*.sln | ||
| 50 | +*.sw? | ||
| 51 | +.history | ||
| 52 | +.cursor |
.gitpod.yml
0 → 100644
.node-version
0 → 100644
.npmrc
0 → 100644
| 1 | +++ a/.npmrc | ||
| 1 | +registry=https://registry.npmmirror.com | ||
| 2 | +public-hoist-pattern[]=lefthook | ||
| 3 | +public-hoist-pattern[]=eslint | ||
| 4 | +public-hoist-pattern[]=prettier | ||
| 5 | +public-hoist-pattern[]=prettier-plugin-tailwindcss | ||
| 6 | +public-hoist-pattern[]=stylelint | ||
| 7 | +public-hoist-pattern[]=*postcss* | ||
| 8 | +public-hoist-pattern[]=@commitlint/* | ||
| 9 | +public-hoist-pattern[]=czg | ||
| 10 | + | ||
| 11 | +strict-peer-dependencies=false | ||
| 12 | +auto-install-peers=true | ||
| 13 | +dedupe-peer-dependents=true |
.prettierignore
0 → 100644
.prettierrc.mjs
0 → 100644
.stylelintignore
0 → 100644
.vscode/extensions.json
0 → 100644
| 1 | +++ a/.vscode/extensions.json | ||
| 1 | +{ | ||
| 2 | + "recommendations": [ | ||
| 3 | + // Vue 3 的语言支持 | ||
| 4 | + "Vue.volar", | ||
| 5 | + // 将 ESLint JavaScript 集成到 VS Code 中。 | ||
| 6 | + "dbaeumer.vscode-eslint", | ||
| 7 | + // Visual Studio Code 的官方 Stylelint 扩展 | ||
| 8 | + "stylelint.vscode-stylelint", | ||
| 9 | + // 使用 Prettier 的代码格式化程序 | ||
| 10 | + "esbenp.prettier-vscode", | ||
| 11 | + // 支持 dotenv 文件语法 | ||
| 12 | + "mikestead.dotenv", | ||
| 13 | + // 源代码的拼写检查器 | ||
| 14 | + "streetsidesoftware.code-spell-checker", | ||
| 15 | + // Tailwind CSS 的官方 VS Code 插件 | ||
| 16 | + "bradlc.vscode-tailwindcss", | ||
| 17 | + // iconify 图标插件 | ||
| 18 | + "antfu.iconify", | ||
| 19 | + // i18n 插件 | ||
| 20 | + "Lokalise.i18n-ally", | ||
| 21 | + // CSS 变量提示 | ||
| 22 | + "vunguyentuan.vscode-css-variables", | ||
| 23 | + // 在 package.json 中显示 PNPM catalog 的版本 | ||
| 24 | + "antfu.pnpm-catalog-lens" | ||
| 25 | + ], | ||
| 26 | + "unwantedRecommendations": [ | ||
| 27 | + // 和 volar 冲突 | ||
| 28 | + "octref.vetur" | ||
| 29 | + ] | ||
| 30 | +} |
.vscode/global.code-snippets
0 → 100644
| 1 | +++ a/.vscode/global.code-snippets | ||
| 1 | +{ | ||
| 2 | + "import": { | ||
| 3 | + "scope": "javascript,typescript", | ||
| 4 | + "prefix": "im", | ||
| 5 | + "body": ["import { $2 } from '$1';"], | ||
| 6 | + "description": "Import a module", | ||
| 7 | + }, | ||
| 8 | + "export-all": { | ||
| 9 | + "scope": "javascript,typescript", | ||
| 10 | + "prefix": "ex", | ||
| 11 | + "body": ["export * from '$1';"], | ||
| 12 | + "description": "Export a module", | ||
| 13 | + }, | ||
| 14 | + "vue-script-setup": { | ||
| 15 | + "scope": "vue", | ||
| 16 | + "prefix": "<sc", | ||
| 17 | + "body": [ | ||
| 18 | + "<script setup lang=\"ts\">", | ||
| 19 | + "const props = defineProps<{", | ||
| 20 | + " modelValue?: boolean,", | ||
| 21 | + "}>()", | ||
| 22 | + "$1", | ||
| 23 | + "</script>", | ||
| 24 | + "", | ||
| 25 | + "<template>", | ||
| 26 | + " <div>", | ||
| 27 | + " <slot/>", | ||
| 28 | + " </div>", | ||
| 29 | + "</template>", | ||
| 30 | + ], | ||
| 31 | + }, | ||
| 32 | + "vue-computed": { | ||
| 33 | + "scope": "javascript,typescript,vue", | ||
| 34 | + "prefix": "com", | ||
| 35 | + "body": ["computed(() => { $1 })"], | ||
| 36 | + }, | ||
| 37 | +} |
.vscode/launch.json
0 → 100644
| 1 | +++ a/.vscode/launch.json | ||
| 1 | +{ | ||
| 2 | + "$schema": "https://json.schemastore.org/launchsettings.json", | ||
| 3 | + "version": "0.2.0", | ||
| 4 | + "configurations": [ | ||
| 5 | + { | ||
| 6 | + "type": "chrome", | ||
| 7 | + "name": "vben admin playground dev", | ||
| 8 | + "request": "launch", | ||
| 9 | + "url": "http://localhost:5555", | ||
| 10 | + "env": { "NODE_ENV": "development" }, | ||
| 11 | + "sourceMaps": true, | ||
| 12 | + "webRoot": "${workspaceFolder}/playground" | ||
| 13 | + }, | ||
| 14 | + { | ||
| 15 | + "type": "chrome", | ||
| 16 | + "name": "vben admin antd dev", | ||
| 17 | + "request": "launch", | ||
| 18 | + "url": "http://localhost:5666", | ||
| 19 | + "env": { "NODE_ENV": "development" }, | ||
| 20 | + "sourceMaps": true, | ||
| 21 | + "webRoot": "${workspaceFolder}/apps/web-antd" | ||
| 22 | + }, | ||
| 23 | + { | ||
| 24 | + "type": "chrome", | ||
| 25 | + "name": "vben admin ele dev", | ||
| 26 | + "request": "launch", | ||
| 27 | + "url": "http://localhost:5777", | ||
| 28 | + "env": { "NODE_ENV": "development" }, | ||
| 29 | + "sourceMaps": true, | ||
| 30 | + "webRoot": "${workspaceFolder}/apps/web-payment" | ||
| 31 | + }, | ||
| 32 | + { | ||
| 33 | + "type": "chrome", | ||
| 34 | + "name": "vben admin naive dev", | ||
| 35 | + "request": "launch", | ||
| 36 | + "url": "http://localhost:5888", | ||
| 37 | + "env": { "NODE_ENV": "development" }, | ||
| 38 | + "sourceMaps": true, | ||
| 39 | + "webRoot": "${workspaceFolder}/apps/web-naive" | ||
| 40 | + } | ||
| 41 | + ] | ||
| 42 | +} |
.vscode/settings.json
0 → 100644
| 1 | +++ a/.vscode/settings.json | ||
| 1 | +{ | ||
| 2 | + "tailwindCSS.experimental.configFile": "internal/tailwind-config/src/index.ts", | ||
| 3 | + // workbench | ||
| 4 | + "workbench.list.smoothScrolling": true, | ||
| 5 | + "workbench.startupEditor": "newUntitledFile", | ||
| 6 | + "workbench.tree.indent": 10, | ||
| 7 | + "workbench.editor.highlightModifiedTabs": true, | ||
| 8 | + "workbench.editor.closeOnFileDelete": true, | ||
| 9 | + "workbench.editor.limit.enabled": true, | ||
| 10 | + "workbench.editor.limit.perEditorGroup": true, | ||
| 11 | + "workbench.editor.limit.value": 5, | ||
| 12 | + | ||
| 13 | + // editor | ||
| 14 | + "editor.tabSize": 2, | ||
| 15 | + "editor.detectIndentation": false, | ||
| 16 | + "editor.cursorBlinking": "expand", | ||
| 17 | + "editor.largeFileOptimizations": true, | ||
| 18 | + "editor.accessibilitySupport": "off", | ||
| 19 | + "editor.cursorSmoothCaretAnimation": "on", | ||
| 20 | + "editor.guides.bracketPairs": "active", | ||
| 21 | + "editor.inlineSuggest.enabled": true, | ||
| 22 | + "editor.suggestSelection": "recentlyUsedByPrefix", | ||
| 23 | + "editor.acceptSuggestionOnEnter": "smart", | ||
| 24 | + "editor.suggest.snippetsPreventQuickSuggestions": false, | ||
| 25 | + "editor.stickyScroll.enabled": true, | ||
| 26 | + "editor.hover.sticky": true, | ||
| 27 | + "editor.suggest.insertMode": "replace", | ||
| 28 | + "editor.bracketPairColorization.enabled": true, | ||
| 29 | + "editor.autoClosingBrackets": "beforeWhitespace", | ||
| 30 | + "editor.autoClosingDelete": "always", | ||
| 31 | + "editor.autoClosingOvertype": "always", | ||
| 32 | + "editor.autoClosingQuotes": "beforeWhitespace", | ||
| 33 | + "editor.wordSeparators": "`~!@#%^&*()=+[{]}\\|;:'\",.<>/?", | ||
| 34 | + "editor.codeActionsOnSave": { | ||
| 35 | + "source.fixAll.eslint": "explicit", | ||
| 36 | + "source.fixAll.stylelint": "explicit", | ||
| 37 | + "source.organizeImports": "never" | ||
| 38 | + }, | ||
| 39 | + "editor.defaultFormatter": "esbenp.prettier-vscode", | ||
| 40 | + "[html]": { | ||
| 41 | + "editor.defaultFormatter": "esbenp.prettier-vscode" | ||
| 42 | + }, | ||
| 43 | + "[css]": { | ||
| 44 | + "editor.defaultFormatter": "esbenp.prettier-vscode" | ||
| 45 | + }, | ||
| 46 | + "[scss]": { | ||
| 47 | + "editor.defaultFormatter": "esbenp.prettier-vscode" | ||
| 48 | + }, | ||
| 49 | + "[javascript]": { | ||
| 50 | + "editor.defaultFormatter": "esbenp.prettier-vscode" | ||
| 51 | + }, | ||
| 52 | + "[typescript]": { | ||
| 53 | + "editor.defaultFormatter": "esbenp.prettier-vscode" | ||
| 54 | + }, | ||
| 55 | + "[json]": { | ||
| 56 | + "editor.defaultFormatter": "esbenp.prettier-vscode" | ||
| 57 | + }, | ||
| 58 | + "[markdown]": { | ||
| 59 | + "editor.defaultFormatter": "esbenp.prettier-vscode" | ||
| 60 | + }, | ||
| 61 | + "[jsonc]": { | ||
| 62 | + "editor.defaultFormatter": "esbenp.prettier-vscode" | ||
| 63 | + }, | ||
| 64 | + "[vue]": { | ||
| 65 | + "editor.defaultFormatter": "esbenp.prettier-vscode" | ||
| 66 | + }, | ||
| 67 | + // extensions | ||
| 68 | + "extensions.ignoreRecommendations": true, | ||
| 69 | + | ||
| 70 | + // terminal | ||
| 71 | + "terminal.integrated.cursorBlinking": true, | ||
| 72 | + "terminal.integrated.persistentSessionReviveProcess": "never", | ||
| 73 | + "terminal.integrated.tabs.enabled": true, | ||
| 74 | + "terminal.integrated.scrollback": 10000, | ||
| 75 | + "terminal.integrated.stickyScroll.enabled": true, | ||
| 76 | + | ||
| 77 | + // files | ||
| 78 | + "files.eol": "\n", | ||
| 79 | + "files.insertFinalNewline": true, | ||
| 80 | + "files.simpleDialog.enable": true, | ||
| 81 | + "files.associations": { | ||
| 82 | + "*.ejs": "html", | ||
| 83 | + "*.art": "html", | ||
| 84 | + "**/tsconfig.json": "jsonc", | ||
| 85 | + "*.json": "jsonc", | ||
| 86 | + "package.json": "json" | ||
| 87 | + }, | ||
| 88 | + | ||
| 89 | + "files.exclude": { | ||
| 90 | + "**/.eslintcache": true, | ||
| 91 | + "**/bower_components": true, | ||
| 92 | + "**/.turbo": true, | ||
| 93 | + "**/.idea": true, | ||
| 94 | + "**/.vitepress": true, | ||
| 95 | + "**/tmp": true, | ||
| 96 | + "**/.git": true, | ||
| 97 | + "**/.svn": true, | ||
| 98 | + "**/.hg": true, | ||
| 99 | + "**/CVS": true, | ||
| 100 | + "**/.stylelintcache": true, | ||
| 101 | + "**/.DS_Store": true, | ||
| 102 | + "**/vite.config.mts.*": true, | ||
| 103 | + "**/tea.yaml": true | ||
| 104 | + }, | ||
| 105 | + "files.watcherExclude": { | ||
| 106 | + "**/.git/objects/**": true, | ||
| 107 | + "**/.git/subtree-cache/**": true, | ||
| 108 | + "**/.vscode/**": true, | ||
| 109 | + "**/node_modules/**": true, | ||
| 110 | + "**/tmp/**": true, | ||
| 111 | + "**/bower_components/**": true, | ||
| 112 | + "**/dist/**": true, | ||
| 113 | + "**/yarn.lock": true | ||
| 114 | + }, | ||
| 115 | + | ||
| 116 | + "typescript.tsserver.exclude": ["**/node_modules", "**/dist", "**/.turbo"], | ||
| 117 | + | ||
| 118 | + // search | ||
| 119 | + "search.searchEditor.singleClickBehaviour": "peekDefinition", | ||
| 120 | + "search.followSymlinks": false, | ||
| 121 | + // 在使用搜索功能时,将这些文件夹/文件排除在外 | ||
| 122 | + "search.exclude": { | ||
| 123 | + "**/node_modules": true, | ||
| 124 | + "**/*.log": true, | ||
| 125 | + "**/*.log*": true, | ||
| 126 | + "**/bower_components": true, | ||
| 127 | + "**/dist": true, | ||
| 128 | + "**/elehukouben": true, | ||
| 129 | + "**/.git": true, | ||
| 130 | + "**/.github": true, | ||
| 131 | + "**/.gitignore": true, | ||
| 132 | + "**/.svn": true, | ||
| 133 | + "**/.DS_Store": true, | ||
| 134 | + "**/.vitepress/cache": true, | ||
| 135 | + "**/.idea": true, | ||
| 136 | + "**/.vscode": false, | ||
| 137 | + "**/.yarn": true, | ||
| 138 | + "**/tmp": true, | ||
| 139 | + "*.xml": true, | ||
| 140 | + "out": true, | ||
| 141 | + "dist": true, | ||
| 142 | + "node_modules": true, | ||
| 143 | + "CHANGELOG.md": true, | ||
| 144 | + "**/pnpm-lock.yaml": true, | ||
| 145 | + "**/yarn.lock": true | ||
| 146 | + }, | ||
| 147 | + | ||
| 148 | + "debug.onTaskErrors": "debugAnyway", | ||
| 149 | + "diffEditor.ignoreTrimWhitespace": false, | ||
| 150 | + "npm.packageManager": "pnpm", | ||
| 151 | + | ||
| 152 | + "css.validate": false, | ||
| 153 | + "less.validate": false, | ||
| 154 | + "scss.validate": false, | ||
| 155 | + | ||
| 156 | + // extension | ||
| 157 | + "emmet.showSuggestionsAsSnippets": true, | ||
| 158 | + "emmet.triggerExpansionOnTab": false, | ||
| 159 | + | ||
| 160 | + "errorLens.enabledDiagnosticLevels": ["warning", "error"], | ||
| 161 | + "errorLens.excludeBySource": ["cSpell", "Grammarly", "eslint"], | ||
| 162 | + | ||
| 163 | + "stylelint.enable": true, | ||
| 164 | + "stylelint.packageManager": "pnpm", | ||
| 165 | + "stylelint.validate": ["css", "less", "postcss", "scss", "vue"], | ||
| 166 | + "stylelint.customSyntax": "postcss-html", | ||
| 167 | + "stylelint.snippet": ["css", "less", "postcss", "scss", "vue"], | ||
| 168 | + | ||
| 169 | + "typescript.inlayHints.enumMemberValues.enabled": true, | ||
| 170 | + "typescript.preferences.preferTypeOnlyAutoImports": true, | ||
| 171 | + "typescript.preferences.includePackageJsonAutoImports": "on", | ||
| 172 | + | ||
| 173 | + "eslint.validate": [ | ||
| 174 | + "javascript", | ||
| 175 | + "typescript", | ||
| 176 | + "javascriptreact", | ||
| 177 | + "typescriptreact", | ||
| 178 | + "vue", | ||
| 179 | + "html", | ||
| 180 | + "markdown", | ||
| 181 | + "json", | ||
| 182 | + "jsonc", | ||
| 183 | + "json5" | ||
| 184 | + ], | ||
| 185 | + | ||
| 186 | + "tailwindCSS.experimental.classRegex": [ | ||
| 187 | + ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"] | ||
| 188 | + ], | ||
| 189 | + | ||
| 190 | + "github.copilot.enable": { | ||
| 191 | + "*": true, | ||
| 192 | + "markdown": true, | ||
| 193 | + "plaintext": false, | ||
| 194 | + "yaml": false | ||
| 195 | + }, | ||
| 196 | + | ||
| 197 | + "cssVariables.lookupFiles": ["packages/core/base/design/src/**/*.css"], | ||
| 198 | + | ||
| 199 | + "i18n-ally.localesPaths": [ | ||
| 200 | + "packages/locales/src/langs", | ||
| 201 | + "playground/src/locales/langs", | ||
| 202 | + "apps/*/src/locales/langs" | ||
| 203 | + ], | ||
| 204 | + "i18n-ally.pathMatcher": "{locale}/{namespace}.{ext}", | ||
| 205 | + "i18n-ally.enabledParsers": ["json"], | ||
| 206 | + "i18n-ally.sourceLanguage": "en", | ||
| 207 | + "i18n-ally.displayLanguage": "zh-CN", | ||
| 208 | + "i18n-ally.enabledFrameworks": ["vue", "react"], | ||
| 209 | + "i18n-ally.keystyle": "nested", | ||
| 210 | + "i18n-ally.sortKeys": true, | ||
| 211 | + "i18n-ally.namespace": true, | ||
| 212 | + | ||
| 213 | + // 控制相关文件嵌套展示 | ||
| 214 | + "explorer.fileNesting.enabled": true, | ||
| 215 | + "explorer.fileNesting.expand": false, | ||
| 216 | + "explorer.fileNesting.patterns": { | ||
| 217 | + "*.ts": "$(capture).test.ts, $(capture).test.tsx, $(capture).spec.ts, $(capture).spec.tsx, $(capture).d.ts", | ||
| 218 | + "*.tsx": "$(capture).test.ts, $(capture).test.tsx, $(capture).spec.ts, $(capture).spec.tsx,$(capture).d.ts", | ||
| 219 | + "*.env": "$(capture).env.*", | ||
| 220 | + "README.md": "README*,CHANGELOG*,LICENSE,CNAME", | ||
| 221 | + "package.json": "pnpm-lock.yaml,pnpm-workspace.yaml,.gitattributes,.gitignore,.gitpod.yml,.npmrc,.browserslistrc,.node-version,.git*,.tazerc.json", | ||
| 222 | + "eslint.config.mjs": ".eslintignore,.prettierignore,.stylelintignore,.commitlintrc.*,.prettierrc.*,stylelint.config.*,.lintstagedrc.mjs,cspell.json,lefthook.yml", | ||
| 223 | + "tailwind.config.mjs": "postcss.*" | ||
| 224 | + }, | ||
| 225 | + "commentTranslate.hover.enabled": false, | ||
| 226 | + "commentTranslate.multiLineMerge": true, | ||
| 227 | + "vue.server.hybridMode": true, | ||
| 228 | + "typescript.tsdk": "node_modules/typescript/lib", | ||
| 229 | + "oxc.enable": false, | ||
| 230 | + "cSpell.words": [ | ||
| 231 | + "archiver", | ||
| 232 | + "axios", | ||
| 233 | + "dotenv", | ||
| 234 | + "isequal", | ||
| 235 | + "jspm", | ||
| 236 | + "napi", | ||
| 237 | + "nolebase", | ||
| 238 | + "rollup", | ||
| 239 | + "vitest" | ||
| 240 | + ] | ||
| 241 | +} |
LICENSE
0 → 100644
| 1 | +++ a/LICENSE | ||
| 1 | +MIT License | ||
| 2 | + | ||
| 3 | +Copyright (c) 2024-present, Vben | ||
| 4 | + | ||
| 5 | +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: | ||
| 6 | + | ||
| 7 | +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
| 8 | + | ||
| 9 | +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. |
README.ja-JP.md
0 → 100644
| 1 | +++ a/README.ja-JP.md | ||
| 1 | +<div align="center"> | ||
| 2 | + <a href="https://github.com/anncwb/vue-vben-admin"> | ||
| 3 | + <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp"> | ||
| 4 | + </a> | ||
| 5 | + <br> | ||
| 6 | + <br> | ||
| 7 | + | ||
| 8 | +[](LICENSE) | ||
| 9 | + | ||
| 10 | + <h1>Vue Vben Admin</h1> | ||
| 11 | +</div> | ||
| 12 | + | ||
| 13 | +[](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin)     | ||
| 14 | + | ||
| 15 | +**日本語** | [English](./README.md) | [中文](./README.zh-CN.md) | ||
| 16 | + | ||
| 17 | +## 紹介 | ||
| 18 | + | ||
| 19 | +Vue Vben Adminは、最新の`vue3`、`vite`、`TypeScript`などの主流技術を使用して開発された、無料でオープンソースの中・後端テンプレートです。すぐに使える中・後端のフロントエンドソリューションとして、学習の参考にもなります。 | ||
| 20 | + | ||
| 21 | +## アップグレード通知 | ||
| 22 | + | ||
| 23 | +これは最新バージョン `5.0` であり、以前のバージョンとは互換性がありません。新しいプロジェクトを開始する場合は、最新バージョンを使用することをお勧めします。古いバージョンを表示したい場合は、[v2ブランチ](https://github.com/vbenjs/vue-vben-admin/tree/v2)を使用してください。 | ||
| 24 | + | ||
| 25 | +## 特徴 | ||
| 26 | + | ||
| 27 | +- **最新技術スタック**:Vue 3やViteなどの最先端フロントエンド技術で開発 | ||
| 28 | +- **TypeScript**:アプリケーション規模のJavaScriptのための言語 | ||
| 29 | +- **テーマ**:複数のテーマカラーが利用可能で、カスタマイズオプションも豊富 | ||
| 30 | +- **国際化**:完全な内蔵国際化サポート | ||
| 31 | +- **権限管理**:動的ルートベースの権限生成ソリューションを内蔵 | ||
| 32 | + | ||
| 33 | +## プレビュー | ||
| 34 | + | ||
| 35 | +- [Vben Admin](https://vben.pro/) - フルバージョンの中国語サイト | ||
| 36 | + | ||
| 37 | +テストアカウント:vben/123456 | ||
| 38 | + | ||
| 39 | +<div align="center"> | ||
| 40 | + <img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png"> | ||
| 41 | + <img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png"> | ||
| 42 | + <img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png"> | ||
| 43 | +</div> | ||
| 44 | + | ||
| 45 | +### Gitpodを使用 | ||
| 46 | + | ||
| 47 | +Gitpod(GitHub用の無料オンライン開発環境)でプロジェクトを開き、すぐにコーディングを開始します。 | ||
| 48 | + | ||
| 49 | +[](https://gitpod.io/#https://github.com/vbenjs/vue-vben-admin) | ||
| 50 | + | ||
| 51 | +## ドキュメント | ||
| 52 | + | ||
| 53 | +[ドキュメント](https://doc.vben.pro/) | ||
| 54 | + | ||
| 55 | +## インストールと使用 | ||
| 56 | + | ||
| 57 | +1. プロジェクトコードを取得 | ||
| 58 | + | ||
| 59 | +```bash | ||
| 60 | +git clone https://github.com/vbenjs/vue-vben-admin.git | ||
| 61 | +``` | ||
| 62 | + | ||
| 63 | +2. 依存関係のインストール | ||
| 64 | + | ||
| 65 | +```bash | ||
| 66 | +cd vue-vben-admin | ||
| 67 | +npm i -g corepack | ||
| 68 | +pnpm install | ||
| 69 | +``` | ||
| 70 | + | ||
| 71 | +3. 実行 | ||
| 72 | + | ||
| 73 | +```bash | ||
| 74 | +pnpm dev | ||
| 75 | +``` | ||
| 76 | + | ||
| 77 | +4. ビルド | ||
| 78 | + | ||
| 79 | +```bash | ||
| 80 | +pnpm build | ||
| 81 | +``` | ||
| 82 | + | ||
| 83 | +## 変更ログ | ||
| 84 | + | ||
| 85 | +[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases) | ||
| 86 | + | ||
| 87 | +## 貢献方法 | ||
| 88 | + | ||
| 89 | +ご参加をお待ちしておりますするか、Pull Requestを送信してください。 | ||
| 90 | + | ||
| 91 | +**Pull Request プロセス:** | ||
| 92 | + | ||
| 93 | +1. コードをフォーク | ||
| 94 | +2. 自分のブランチを作成:`git checkout -b feat/xxxx` | ||
| 95 | +3. 変更をコミット:`git commit -am 'feat(function): add xxxxx'` | ||
| 96 | +4. ブランチをプッシュ:`git push origin feat/xxxx` | ||
| 97 | +5. `pull request`を送信 | ||
| 98 | + | ||
| 99 | +## Git貢献提出規則 | ||
| 100 | + | ||
| 101 | +参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 規則 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)) | ||
| 102 | + | ||
| 103 | +- `feat` 新機能の追加 | ||
| 104 | +- `fix` 問題/バグの修正 | ||
| 105 | +- `style` コードスタイルに関連し、実行結果に影響しない | ||
| 106 | +- `perf` 最適化/パフォーマンス向上 | ||
| 107 | +- `refactor` リファクタリング | ||
| 108 | +- `revert` 変更の取り消し | ||
| 109 | +- `test` テスト関連 | ||
| 110 | +- `docs` ドキュメント/注釈 | ||
| 111 | +- `chore` 依存関係の更新/スキャフォールディング設定の変更など | ||
| 112 | +- `ci` 継続的インテグレーション | ||
| 113 | +- `types` 型定義ファイルの変更 | ||
| 114 | + | ||
| 115 | +## ブラウザサポート | ||
| 116 | + | ||
| 117 | +ローカル開発には `Chrome 80+` ブラウザを推奨します | ||
| 118 | + | ||
| 119 | +モダンブラウザをサポートし、IEはサポートしません | ||
| 120 | + | ||
| 121 | +| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | | ||
| 122 | +| :-: | :-: | :-: | :-: | | ||
| 123 | +| 最新2バージョン | 最新2バージョン | 最新2バージョン | 最新2バージョン | | ||
| 124 | + | ||
| 125 | +## メンテナー | ||
| 126 | + | ||
| 127 | +[@Vben](https://github.com/anncwb) | ||
| 128 | + | ||
| 129 | +## スター歴史 | ||
| 130 | + | ||
| 131 | +[](https://star-history.com/#vbenjs/vue-vben-admin&Date) | ||
| 132 | + | ||
| 133 | +## 寄付 | ||
| 134 | + | ||
| 135 | +このプロジェクトが役に立つと思われた場合、作者にコーヒーを一杯おごってサポートを示すことができます! | ||
| 136 | + | ||
| 137 | + | ||
| 138 | + | ||
| 139 | +<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a> | ||
| 140 | + | ||
| 141 | +## 貢献者 | ||
| 142 | + | ||
| 143 | +<a href="https://openomy.app/github/vbenjs/vue-vben-admin" target="_blank" style="display: block; width: 100%;" align="center"> | ||
| 144 | + <img src="https://openomy.app/svg?repo=vbenjs/vue-vben-admin&chart=bubble&latestMonth=3" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" /> | ||
| 145 | + </a> | ||
| 146 | + | ||
| 147 | +<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors"> | ||
| 148 | + <img alt="Contributors" src="https://contrib.rocks/image?repo=vbenjs/vue-vben-admin" /> | ||
| 149 | +</a> | ||
| 150 | + | ||
| 151 | +## Discord | ||
| 152 | + | ||
| 153 | +- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions) | ||
| 154 | + | ||
| 155 | +## ライセンス | ||
| 156 | + | ||
| 157 | +[MIT © Vben-2020](./LICENSE) |
README.md
0 → 100644
| 1 | +++ a/README.md | ||
| 1 | +<div align="center"> | ||
| 2 | + <a href="https://github.com/anncwb/vue-vben-admin"> | ||
| 3 | + <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp"> | ||
| 4 | + </a> | ||
| 5 | + <br> | ||
| 6 | + <br> | ||
| 7 | + | ||
| 8 | +[](LICENSE) | ||
| 9 | + | ||
| 10 | + <h1>Vue Vben Admin</h1> | ||
| 11 | +</div> | ||
| 12 | + | ||
| 13 | +[](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin) [](https://github.com/vbenjs/vue-vben-admin/actions/workflows/codeql.yml) [](https://github.com/vbenjs/vue-vben-admin/actions/workflows/build.yml) [](https://github.com/vbenjs/vue-vben-admin/actions/workflows/ci.yml) [](https://github.com/vbenjs/vue-vben-admin/actions/workflows/deploy.yml) | ||
| 14 | + | ||
| 15 | +**English** | [中文](./README.zh-CN.md) | [日本語](./README.ja-JP.md) | ||
| 16 | + | ||
| 17 | +## Introduction | ||
| 18 | + | ||
| 19 | +Vue Vben Admin is a free and open source middle and back-end template. Using the latest `vue3`, `vite`, `TypeScript` and other mainstream technology development, the out-of-the-box middle and back-end front-end solutions can also be used for learning reference. | ||
| 20 | + | ||
| 21 | +## Upgrade Notice | ||
| 22 | + | ||
| 23 | +This is the latest version, 5.0, and it is not compatible with previous versions. If you are starting a new project, it is recommended to use the latest version. If you wish to view the old version, please use the [v2 branch](https://github.com/vbenjs/vue-vben-admin/tree/v2). | ||
| 24 | + | ||
| 25 | +## Features | ||
| 26 | + | ||
| 27 | +- **Latest Technology Stack**: Developed with cutting-edge front-end technologies like Vue 3 and Vite | ||
| 28 | +- **TypeScript**: A language for application-scale JavaScript | ||
| 29 | +- **Themes**: Multiple theme colors available with customizable options | ||
| 30 | +- **Internationalization**: Comprehensive built-in internationalization support | ||
| 31 | +- **Permissions**: Built-in solution for dynamic route-based permission generation | ||
| 32 | + | ||
| 33 | +## Preview | ||
| 34 | + | ||
| 35 | +- [Vben Admin](https://vben.pro/) - Full version Chinese site | ||
| 36 | + | ||
| 37 | +Test Account: vben/123456 | ||
| 38 | + | ||
| 39 | +<div align="center"> | ||
| 40 | + <img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png"> | ||
| 41 | + <img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png"> | ||
| 42 | + <img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png"> | ||
| 43 | +</div> | ||
| 44 | + | ||
| 45 | +### Use Gitpod | ||
| 46 | + | ||
| 47 | +Open the project in Gitpod (free online dev environment for GitHub) and start coding immediately. | ||
| 48 | + | ||
| 49 | +[](https://gitpod.io/#https://github.com/vbenjs/vue-vben-admin) | ||
| 50 | + | ||
| 51 | +## Documentation | ||
| 52 | + | ||
| 53 | +[Document](https://doc.vben.pro/) | ||
| 54 | + | ||
| 55 | +## Install and Use | ||
| 56 | + | ||
| 57 | +1. Get the project code | ||
| 58 | + | ||
| 59 | +```bash | ||
| 60 | +git clone https://github.com/vbenjs/vue-vben-admin.git | ||
| 61 | +``` | ||
| 62 | + | ||
| 63 | +2. Install dependencies | ||
| 64 | + | ||
| 65 | +```bash | ||
| 66 | +cd vue-vben-admin | ||
| 67 | +npm i -g corepack | ||
| 68 | +pnpm install | ||
| 69 | +``` | ||
| 70 | + | ||
| 71 | +3. Run | ||
| 72 | + | ||
| 73 | +```bash | ||
| 74 | +pnpm dev | ||
| 75 | +``` | ||
| 76 | + | ||
| 77 | +4. Build | ||
| 78 | + | ||
| 79 | +```bash | ||
| 80 | +pnpm build | ||
| 81 | +``` | ||
| 82 | + | ||
| 83 | +## Change Log | ||
| 84 | + | ||
| 85 | +[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases) | ||
| 86 | + | ||
| 87 | +## How to Contribute | ||
| 88 | + | ||
| 89 | +You are very welcome to join! [Raise an issue](https://github.com/anncwb/vue-vben-admin/issues/new/choose) or submit a Pull Request. | ||
| 90 | + | ||
| 91 | +**Pull Request Process:** | ||
| 92 | + | ||
| 93 | +1. Fork the code | ||
| 94 | +2. Create your branch: `git checkout -b feat/xxxx` | ||
| 95 | +3. Submit your changes: `git commit -am 'feat(function): add xxxxx'` | ||
| 96 | +4. Push your branch: `git push origin feat/xxxx` | ||
| 97 | +5. Submit `pull request` | ||
| 98 | + | ||
| 99 | +## Git Contribution Submission Specification | ||
| 100 | + | ||
| 101 | +Reference [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) specification ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)) | ||
| 102 | + | ||
| 103 | +- `feat` Add new features | ||
| 104 | +- `fix` Fix the problem/BUG | ||
| 105 | +- `style` The code style is related and does not affect the running result | ||
| 106 | +- `perf` Optimization/performance improvement | ||
| 107 | +- `refactor` Refactor | ||
| 108 | +- `revert` Undo edit | ||
| 109 | +- `test` Test related | ||
| 110 | +- `docs` Documentation/notes | ||
| 111 | +- `chore` Dependency update/scaffolding configuration modification etc. | ||
| 112 | +- `ci` Continuous integration | ||
| 113 | +- `types` Type definition file changes | ||
| 114 | + | ||
| 115 | +## Browser Support | ||
| 116 | + | ||
| 117 | +The `Chrome 80+` browser is recommended for local development | ||
| 118 | + | ||
| 119 | +Support modern browsers, not IE | ||
| 120 | + | ||
| 121 | +| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | | ||
| 122 | +| :-: | :-: | :-: | :-: | | ||
| 123 | +| last 2 versions | last 2 versions | last 2 versions | last 2 versions | | ||
| 124 | + | ||
| 125 | +## Maintainer | ||
| 126 | + | ||
| 127 | +[@Vben](https://github.com/anncwb) | ||
| 128 | + | ||
| 129 | +## Star History | ||
| 130 | + | ||
| 131 | +[](https://star-history.com/#vbenjs/vue-vben-admin&Date) | ||
| 132 | + | ||
| 133 | +## Donate | ||
| 134 | + | ||
| 135 | +If you think this project is helpful to you, you can help the author buy a cup of coffee to show your support! | ||
| 136 | + | ||
| 137 | + | ||
| 138 | + | ||
| 139 | +<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aee;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a> | ||
| 140 | + | ||
| 141 | +## Contributors | ||
| 142 | + | ||
| 143 | +<a href="https://openomy.app/github/vbenjs/vue-vben-admin" target="_blank" style="display: block; width: 100%;" align="center"> | ||
| 144 | + <img src="https://openomy.app/svg?repo=vbenjs/vue-vben-admin&chart=bubble&latestMonth=3" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" /> | ||
| 145 | + </a> | ||
| 146 | + | ||
| 147 | +<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors"> | ||
| 148 | + <img alt="Contributors" src="https://contrib.rocks/image?repo=vbenjs/vue-vben-admin" /> | ||
| 149 | +</a> | ||
| 150 | + | ||
| 151 | +## Discord | ||
| 152 | + | ||
| 153 | +- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions) | ||
| 154 | + | ||
| 155 | +## License | ||
| 156 | + | ||
| 157 | +[MIT © Vben-2020](./LICENSE) |
README.zh-CN.md
0 → 100644
| 1 | +++ a/README.zh-CN.md | ||
| 1 | +<div align="center"> | ||
| 2 | + <a href="https://github.com/anncwb/vue-vben-admin"> | ||
| 3 | + <img alt="VbenAdmin Logo" width="215" src="https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp"> | ||
| 4 | + </a> | ||
| 5 | + <br> | ||
| 6 | + <br> | ||
| 7 | + | ||
| 8 | +[](LICENSE) | ||
| 9 | + | ||
| 10 | + <h1>Vue Vben Admin</h1> | ||
| 11 | +</div> | ||
| 12 | + | ||
| 13 | +[](https://sonarcloud.io/summary/new_code?id=vbenjs_vue-vben-admin)     | ||
| 14 | + | ||
| 15 | +**中文** | [English](./README.md) | [日本語](./README.ja-JP.md) | ||
| 16 | + | ||
| 17 | +## 简介 | ||
| 18 | + | ||
| 19 | +Vue Vben Admin 是 Vue Vben Admin 的升级版本。作为一个免费开源的中后台模板,它采用了最新的 Vue 3、Vite、TypeScript 等主流技术开发,开箱即用,可用于中后台前端开发,也适合学习参考。 | ||
| 20 | + | ||
| 21 | +## 升级提示 | ||
| 22 | + | ||
| 23 | +该版本为最新版本 `5.0`,与其他版本不兼容,如果你是新项目,建议使用最新版本。如果你想查看旧版本,请使用 [v2 分支](https://github.com/vbenjs/vue-vben-admin/tree/v2) | ||
| 24 | + | ||
| 25 | +## 特性 | ||
| 26 | + | ||
| 27 | +- **最新技术栈**:使用 Vue3/vite 等前端前沿技术开发 | ||
| 28 | +- **TypeScript**:应用程序级 JavaScript 的语言 | ||
| 29 | +- **主题**:提供多套主题色彩,可配置自定义主题 | ||
| 30 | +- **国际化**:内置完善的国际化方案 | ||
| 31 | +- **权限**:内置完善的动态路由权限生成方案 | ||
| 32 | + | ||
| 33 | +## 预览 | ||
| 34 | + | ||
| 35 | +- [Vben Admin](https://vben.pro/) - 完整版中文站点 | ||
| 36 | + | ||
| 37 | +测试账号:vben/123456 | ||
| 38 | + | ||
| 39 | +<div align="center"> | ||
| 40 | + <img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview1.png"> | ||
| 41 | + <img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview2.png"> | ||
| 42 | + <img alt="VbenAdmin Logo" width="100%" src="https://anncwb.github.io/anncwb/images/preview3.png"> | ||
| 43 | +</div> | ||
| 44 | + | ||
| 45 | +### 使用 Gitpod | ||
| 46 | + | ||
| 47 | +在 Gitpod(适用于 GitHub 的免费在线开发环境)中打开项目,并立即开始编码。 | ||
| 48 | + | ||
| 49 | +[](https://gitpod.io/#https://github.com/vbenjs/vue-vben-admin) | ||
| 50 | + | ||
| 51 | +## 文档 | ||
| 52 | + | ||
| 53 | +[文档地址](https://doc.vben.pro/) | ||
| 54 | + | ||
| 55 | +## 安装使用 | ||
| 56 | + | ||
| 57 | +1. 获取项目代码 | ||
| 58 | + | ||
| 59 | +```bash | ||
| 60 | +git clone https://github.com/vbenjs/vue-vben-admin.git | ||
| 61 | +``` | ||
| 62 | + | ||
| 63 | +2. 安装依赖 | ||
| 64 | + | ||
| 65 | +```bash | ||
| 66 | +cd vue-vben-admin | ||
| 67 | +npm i -g corepack | ||
| 68 | +pnpm install | ||
| 69 | +``` | ||
| 70 | + | ||
| 71 | +3. 运行 | ||
| 72 | + | ||
| 73 | +```bash | ||
| 74 | +pnpm dev | ||
| 75 | +``` | ||
| 76 | + | ||
| 77 | +4. 打包 | ||
| 78 | + | ||
| 79 | +```bash | ||
| 80 | +pnpm build | ||
| 81 | +``` | ||
| 82 | + | ||
| 83 | +## 更新日志 | ||
| 84 | + | ||
| 85 | +[CHANGELOG](https://github.com/vbenjs/vue-vben-admin/releases) | ||
| 86 | + | ||
| 87 | +## 如何贡献 | ||
| 88 | + | ||
| 89 | +非常欢迎你的加入 或者提交一个 Pull Request。 | ||
| 90 | + | ||
| 91 | +**Pull Request 流程:** | ||
| 92 | + | ||
| 93 | +1. Fork 代码 | ||
| 94 | +2. 创建自己的分支:`git checkout -b feature/xxxx` | ||
| 95 | +3. 提交你的修改:`git commit -am 'feat(function): add xxxxx'` | ||
| 96 | +4. 推送您的分支:`git push origin feature/xxxx` | ||
| 97 | +5. 提交 `pull request` | ||
| 98 | + | ||
| 99 | +## Git 贡献提交规范 | ||
| 100 | + | ||
| 101 | +参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular)) | ||
| 102 | + | ||
| 103 | +- `feat` 增加新功能 | ||
| 104 | +- `fix` 修复问题/BUG | ||
| 105 | +- `style` 代码风格相关无影响运行结果的 | ||
| 106 | +- `perf` 优化/性能提升 | ||
| 107 | +- `refactor` 重构 | ||
| 108 | +- `revert` 撤销修改 | ||
| 109 | +- `test` 测试相关 | ||
| 110 | +- `docs` 文档/注释 | ||
| 111 | +- `chore` 依赖更新/脚手架配置修改等 | ||
| 112 | +- `ci` 持续集成 | ||
| 113 | +- `types` 类型定义文件更改 | ||
| 114 | + | ||
| 115 | +## 浏览器支持 | ||
| 116 | + | ||
| 117 | +本地开发推荐使用 `Chrome 80+` 浏览器 | ||
| 118 | + | ||
| 119 | +支持现代浏览器,不支持 IE | ||
| 120 | + | ||
| 121 | +| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | | ||
| 122 | +| :-: | :-: | :-: | :-: | | ||
| 123 | +| last 2 versions | last 2 versions | last 2 versions | last 2 versions | | ||
| 124 | + | ||
| 125 | +## 维护者 | ||
| 126 | + | ||
| 127 | +[@Vben](https://github.com/anncwb) | ||
| 128 | + | ||
| 129 | +## Star 历史 | ||
| 130 | + | ||
| 131 | +[](https://star-history.com/#vbenjs/vue-vben-admin&Date) | ||
| 132 | + | ||
| 133 | +## 捐赠 | ||
| 134 | + | ||
| 135 | +如果你觉得这个项目对你有帮助,你可以帮作者买一杯咖啡表示支持! | ||
| 136 | + | ||
| 137 | + | ||
| 138 | + | ||
| 139 | +<a style="display: block;width: 100px;height: 50px;line-height: 50px; color: #fff;text-align: center; background: #408aed;border-radius: 4px;" href="https://www.paypal.com/paypalme/cvvben">Paypal Me</a> | ||
| 140 | + | ||
| 141 | +## 贡献者 | ||
| 142 | + | ||
| 143 | +<a href="https://openomy.app/github/vbenjs/vue-vben-admin" target="_blank" style="display: block; width: 100%;" align="center"> | ||
| 144 | + <img src="https://openomy.app/svg?repo=vbenjs/vue-vben-admin&chart=bubble&latestMonth=3" target="_blank" alt="Contribution Leaderboard" style="display: block; width: 100%;" /> | ||
| 145 | + </a> | ||
| 146 | + | ||
| 147 | +<a href="https://github.com/vbenjs/vue-vben-admin/graphs/contributors"> | ||
| 148 | + <img alt="Contributors" src="https://contrib.rocks/image?repo=vbenjs/vue-vben-admin" /> | ||
| 149 | +</a> | ||
| 150 | + | ||
| 151 | +## Discord | ||
| 152 | + | ||
| 153 | +- [Github Discussions](https://github.com/anncwb/vue-vben-admin/discussions) | ||
| 154 | + | ||
| 155 | +## 许可证 | ||
| 156 | + | ||
| 157 | +[MIT © Vben-2020](./LICENSE) |
apps/web-payment/.env
0 → 100644
apps/web-payment/.env.analyze
0 → 100644
apps/web-payment/.env.development
0 → 100644
| 1 | +++ a/apps/web-payment/.env.development | ||
| 1 | +# 端口号 | ||
| 2 | +VITE_PORT=5777 | ||
| 3 | + | ||
| 4 | +VITE_BASE=/ | ||
| 5 | + | ||
| 6 | +# 接口地址 | ||
| 7 | +VITE_GLOB_API_URL=/api | ||
| 8 | + | ||
| 9 | +# 是否开启 Nitro Mock服务,true 为开启,false 为关闭 | ||
| 10 | +VITE_NITRO_MOCK=true | ||
| 11 | + | ||
| 12 | +# 是否打开 devtools,true 为打开,false 为关闭 | ||
| 13 | +VITE_DEVTOOLS=false | ||
| 14 | + | ||
| 15 | +# 是否注入全局loading | ||
| 16 | +VITE_INJECT_APP_LOADING=false | ||
| 17 | + |
apps/web-payment/.env.production
0 → 100644
| 1 | +++ a/apps/web-payment/.env.production | ||
| 1 | +VITE_BASE=/ | ||
| 2 | + | ||
| 3 | +# 接口地址 | ||
| 4 | +VITE_GLOB_API_URL=https://mock-napi.vben.pro/api | ||
| 5 | + | ||
| 6 | +# 是否开启压缩,可以设置为 none, brotli, gzip | ||
| 7 | +VITE_COMPRESS=none | ||
| 8 | + | ||
| 9 | +# 是否开启 PWA | ||
| 10 | +VITE_PWA=false | ||
| 11 | + | ||
| 12 | +# vue-router 的模式 | ||
| 13 | +VITE_ROUTER_HISTORY=hash | ||
| 14 | + | ||
| 15 | +# 是否注入全局loading | ||
| 16 | +VITE_INJECT_APP_LOADING=true | ||
| 17 | + | ||
| 18 | +# 打包后是否生成dist.zip | ||
| 19 | +VITE_ARCHIVER=true |
apps/web-payment/index.html
0 → 100644
| 1 | +++ a/apps/web-payment/index.html | ||
| 1 | +<!doctype html> | ||
| 2 | +<html lang="zh"> | ||
| 3 | + <head> | ||
| 4 | + <meta charset="UTF-8" /> | ||
| 5 | + <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" /> | ||
| 6 | + <meta name="renderer" content="webkit" /> | ||
| 7 | + <meta name="description" content="A Modern Back-end Management System" /> | ||
| 8 | + <meta name="keywords" content="Vben Admin Vue3 Vite" /> | ||
| 9 | + <meta name="author" content="Vben" /> | ||
| 10 | + <meta | ||
| 11 | + name="viewport" | ||
| 12 | + content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0" | ||
| 13 | + /> | ||
| 14 | + <!-- 由 vite 注入 VITE_APP_TITLE 变量,在 .env 文件内配置 --> | ||
| 15 | + <title><%= VITE_APP_TITLE %></title> | ||
| 16 | + <link rel="icon" href="/favicon.ico" /> | ||
| 17 | + </head> | ||
| 18 | + <body> | ||
| 19 | + <div id="app"></div> | ||
| 20 | + <script type="module" src="/src/main.ts"></script> | ||
| 21 | + </body> | ||
| 22 | +</html> |
apps/web-payment/package.json
0 → 100644
| 1 | +++ a/apps/web-payment/package.json | ||
| 1 | +{ | ||
| 2 | + "name": "@vben/web-payment", | ||
| 3 | + "version": "5.5.9", | ||
| 4 | + "homepage": "https://vben.pro", | ||
| 5 | + "bugs": "https://github.com/vbenjs/vue-vben-admin/issues", | ||
| 6 | + "repository": { | ||
| 7 | + "type": "git", | ||
| 8 | + "url": "git+https://github.com/vbenjs/vue-vben-admin.git", | ||
| 9 | + "directory": "apps/web-payment" | ||
| 10 | + }, | ||
| 11 | + "license": "MIT", | ||
| 12 | + "author": { | ||
| 13 | + "name": "vben", | ||
| 14 | + "email": "ann.vben@gmail.com", | ||
| 15 | + "url": "https://github.com/anncwb" | ||
| 16 | + }, | ||
| 17 | + "type": "module", | ||
| 18 | + "scripts": { | ||
| 19 | + "build": "pnpm vite build --mode production", | ||
| 20 | + "build:analyze": "pnpm vite build --mode analyze", | ||
| 21 | + "dev": "pnpm vite --mode development", | ||
| 22 | + "preview": "vite preview", | ||
| 23 | + "typecheck": "vue-tsc --noEmit --skipLibCheck" | ||
| 24 | + }, | ||
| 25 | + "imports": { | ||
| 26 | + "#/*": "./src/*" | ||
| 27 | + }, | ||
| 28 | + "dependencies": { | ||
| 29 | + "@vben/access": "workspace:*", | ||
| 30 | + "@vben/common-ui": "workspace:*", | ||
| 31 | + "@vben/constants": "workspace:*", | ||
| 32 | + "@vben/hooks": "workspace:*", | ||
| 33 | + "@vben/icons": "workspace:*", | ||
| 34 | + "@vben/layouts": "workspace:*", | ||
| 35 | + "@vben/locales": "workspace:*", | ||
| 36 | + "@vben/plugins": "workspace:*", | ||
| 37 | + "@vben/preferences": "workspace:*", | ||
| 38 | + "@vben/request": "workspace:*", | ||
| 39 | + "@vben/stores": "workspace:*", | ||
| 40 | + "@vben/styles": "workspace:*", | ||
| 41 | + "@vben/types": "workspace:*", | ||
| 42 | + "@vben/utils": "workspace:*", | ||
| 43 | + "@vueuse/core": "catalog:", | ||
| 44 | + "dayjs": "catalog:", | ||
| 45 | + "element-plus": "catalog:", | ||
| 46 | + "pinia": "catalog:", | ||
| 47 | + "vue": "catalog:", | ||
| 48 | + "vue-router": "catalog:" | ||
| 49 | + }, | ||
| 50 | + "devDependencies": { | ||
| 51 | + "unplugin-element-plus": "catalog:" | ||
| 52 | + } | ||
| 53 | +} |
apps/web-payment/postcss.config.mjs
0 → 100644
apps/web-payment/public/favicon.ico
0 → 100644
No preview for this file type
apps/web-payment/src/adapter/component/index.ts
0 → 100644
| 1 | +++ a/apps/web-payment/src/adapter/component/index.ts | ||
| 1 | +/** | ||
| 2 | + * 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用 | ||
| 3 | + * 可用于 vben-form、vben-modal、vben-drawer 等组件使用, | ||
| 4 | + */ | ||
| 5 | + | ||
| 6 | +import type { Component } from 'vue'; | ||
| 7 | + | ||
| 8 | +import type { BaseFormComponentType } from '@vben/common-ui'; | ||
| 9 | +import type { Recordable } from '@vben/types'; | ||
| 10 | + | ||
| 11 | +import { defineAsyncComponent, defineComponent, h, ref } from 'vue'; | ||
| 12 | + | ||
| 13 | +import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui'; | ||
| 14 | +import { $t } from '@vben/locales'; | ||
| 15 | + | ||
| 16 | +import { ElNotification } from 'element-plus'; | ||
| 17 | + | ||
| 18 | +const ElButton = defineAsyncComponent(() => | ||
| 19 | + Promise.all([ | ||
| 20 | + import('element-plus/es/components/button/index'), | ||
| 21 | + import('element-plus/es/components/button/style/css'), | ||
| 22 | + ]).then(([res]) => res.ElButton), | ||
| 23 | +); | ||
| 24 | +const ElCheckbox = defineAsyncComponent(() => | ||
| 25 | + Promise.all([ | ||
| 26 | + import('element-plus/es/components/checkbox/index'), | ||
| 27 | + import('element-plus/es/components/checkbox/style/css'), | ||
| 28 | + ]).then(([res]) => res.ElCheckbox), | ||
| 29 | +); | ||
| 30 | +const ElCheckboxButton = defineAsyncComponent(() => | ||
| 31 | + Promise.all([ | ||
| 32 | + import('element-plus/es/components/checkbox/index'), | ||
| 33 | + import('element-plus/es/components/checkbox-button/style/css'), | ||
| 34 | + ]).then(([res]) => res.ElCheckboxButton), | ||
| 35 | +); | ||
| 36 | +const ElCheckboxGroup = defineAsyncComponent(() => | ||
| 37 | + Promise.all([ | ||
| 38 | + import('element-plus/es/components/checkbox/index'), | ||
| 39 | + import('element-plus/es/components/checkbox-group/style/css'), | ||
| 40 | + ]).then(([res]) => res.ElCheckboxGroup), | ||
| 41 | +); | ||
| 42 | +const ElDatePicker = defineAsyncComponent(() => | ||
| 43 | + Promise.all([ | ||
| 44 | + import('element-plus/es/components/date-picker/index'), | ||
| 45 | + import('element-plus/es/components/date-picker/style/css'), | ||
| 46 | + ]).then(([res]) => res.ElDatePicker), | ||
| 47 | +); | ||
| 48 | +const ElDivider = defineAsyncComponent(() => | ||
| 49 | + Promise.all([ | ||
| 50 | + import('element-plus/es/components/divider/index'), | ||
| 51 | + import('element-plus/es/components/divider/style/css'), | ||
| 52 | + ]).then(([res]) => res.ElDivider), | ||
| 53 | +); | ||
| 54 | +const ElInput = defineAsyncComponent(() => | ||
| 55 | + Promise.all([ | ||
| 56 | + import('element-plus/es/components/input/index'), | ||
| 57 | + import('element-plus/es/components/input/style/css'), | ||
| 58 | + ]).then(([res]) => res.ElInput), | ||
| 59 | +); | ||
| 60 | +const ElInputNumber = defineAsyncComponent(() => | ||
| 61 | + Promise.all([ | ||
| 62 | + import('element-plus/es/components/input-number/index'), | ||
| 63 | + import('element-plus/es/components/input-number/style/css'), | ||
| 64 | + ]).then(([res]) => res.ElInputNumber), | ||
| 65 | +); | ||
| 66 | +const ElRadio = defineAsyncComponent(() => | ||
| 67 | + Promise.all([ | ||
| 68 | + import('element-plus/es/components/radio/index'), | ||
| 69 | + import('element-plus/es/components/radio/style/css'), | ||
| 70 | + ]).then(([res]) => res.ElRadio), | ||
| 71 | +); | ||
| 72 | +const ElRadioButton = defineAsyncComponent(() => | ||
| 73 | + Promise.all([ | ||
| 74 | + import('element-plus/es/components/radio/index'), | ||
| 75 | + import('element-plus/es/components/radio-button/style/css'), | ||
| 76 | + ]).then(([res]) => res.ElRadioButton), | ||
| 77 | +); | ||
| 78 | +const ElRadioGroup = defineAsyncComponent(() => | ||
| 79 | + Promise.all([ | ||
| 80 | + import('element-plus/es/components/radio/index'), | ||
| 81 | + import('element-plus/es/components/radio-group/style/css'), | ||
| 82 | + ]).then(([res]) => res.ElRadioGroup), | ||
| 83 | +); | ||
| 84 | +const ElSelectV2 = defineAsyncComponent(() => | ||
| 85 | + Promise.all([ | ||
| 86 | + import('element-plus/es/components/select-v2/index'), | ||
| 87 | + import('element-plus/es/components/select-v2/style/css'), | ||
| 88 | + ]).then(([res]) => res.ElSelectV2), | ||
| 89 | +); | ||
| 90 | +const ElSpace = defineAsyncComponent(() => | ||
| 91 | + Promise.all([ | ||
| 92 | + import('element-plus/es/components/space/index'), | ||
| 93 | + import('element-plus/es/components/space/style/css'), | ||
| 94 | + ]).then(([res]) => res.ElSpace), | ||
| 95 | +); | ||
| 96 | +const ElSwitch = defineAsyncComponent(() => | ||
| 97 | + Promise.all([ | ||
| 98 | + import('element-plus/es/components/switch/index'), | ||
| 99 | + import('element-plus/es/components/switch/style/css'), | ||
| 100 | + ]).then(([res]) => res.ElSwitch), | ||
| 101 | +); | ||
| 102 | +const ElTimePicker = defineAsyncComponent(() => | ||
| 103 | + Promise.all([ | ||
| 104 | + import('element-plus/es/components/time-picker/index'), | ||
| 105 | + import('element-plus/es/components/time-picker/style/css'), | ||
| 106 | + ]).then(([res]) => res.ElTimePicker), | ||
| 107 | +); | ||
| 108 | +const ElTreeSelect = defineAsyncComponent(() => | ||
| 109 | + Promise.all([ | ||
| 110 | + import('element-plus/es/components/tree-select/index'), | ||
| 111 | + import('element-plus/es/components/tree-select/style/css'), | ||
| 112 | + ]).then(([res]) => res.ElTreeSelect), | ||
| 113 | +); | ||
| 114 | +const ElUpload = defineAsyncComponent(() => | ||
| 115 | + Promise.all([ | ||
| 116 | + import('element-plus/es/components/upload/index'), | ||
| 117 | + import('element-plus/es/components/upload/style/css'), | ||
| 118 | + ]).then(([res]) => res.ElUpload), | ||
| 119 | +); | ||
| 120 | + | ||
| 121 | +const withDefaultPlaceholder = <T extends Component>( | ||
| 122 | + component: T, | ||
| 123 | + type: 'input' | 'select', | ||
| 124 | + componentProps: Recordable<any> = {}, | ||
| 125 | +) => { | ||
| 126 | + return defineComponent({ | ||
| 127 | + name: component.name, | ||
| 128 | + inheritAttrs: false, | ||
| 129 | + setup: (props: any, { attrs, expose, slots }) => { | ||
| 130 | + const placeholder = | ||
| 131 | + props?.placeholder || | ||
| 132 | + attrs?.placeholder || | ||
| 133 | + $t(`ui.placeholder.${type}`); | ||
| 134 | + // 透传组件暴露的方法 | ||
| 135 | + const innerRef = ref(); | ||
| 136 | + expose( | ||
| 137 | + new Proxy( | ||
| 138 | + {}, | ||
| 139 | + { | ||
| 140 | + get: (_target, key) => innerRef.value?.[key], | ||
| 141 | + has: (_target, key) => key in (innerRef.value || {}), | ||
| 142 | + }, | ||
| 143 | + ), | ||
| 144 | + ); | ||
| 145 | + return () => | ||
| 146 | + h( | ||
| 147 | + component, | ||
| 148 | + { ...componentProps, placeholder, ...props, ...attrs, ref: innerRef }, | ||
| 149 | + slots, | ||
| 150 | + ); | ||
| 151 | + }, | ||
| 152 | + }); | ||
| 153 | +}; | ||
| 154 | + | ||
| 155 | +// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明 | ||
| 156 | +export type ComponentType = | ||
| 157 | + | 'ApiSelect' | ||
| 158 | + | 'ApiTreeSelect' | ||
| 159 | + | 'Checkbox' | ||
| 160 | + | 'CheckboxGroup' | ||
| 161 | + | 'DatePicker' | ||
| 162 | + | 'Divider' | ||
| 163 | + | 'IconPicker' | ||
| 164 | + | 'Input' | ||
| 165 | + | 'InputNumber' | ||
| 166 | + | 'RadioGroup' | ||
| 167 | + | 'Select' | ||
| 168 | + | 'Space' | ||
| 169 | + | 'Switch' | ||
| 170 | + | 'TimePicker' | ||
| 171 | + | 'TreeSelect' | ||
| 172 | + | 'Upload' | ||
| 173 | + | BaseFormComponentType; | ||
| 174 | + | ||
| 175 | +async function initComponentAdapter() { | ||
| 176 | + const components: Partial<Record<ComponentType, Component>> = { | ||
| 177 | + // 如果你的组件体积比较大,可以使用异步加载 | ||
| 178 | + // Button: () => | ||
| 179 | + // import('xxx').then((res) => res.Button), | ||
| 180 | + ApiSelect: withDefaultPlaceholder( | ||
| 181 | + { | ||
| 182 | + ...ApiComponent, | ||
| 183 | + name: 'ApiSelect', | ||
| 184 | + }, | ||
| 185 | + 'select', | ||
| 186 | + { | ||
| 187 | + component: ElSelectV2, | ||
| 188 | + loadingSlot: 'loading', | ||
| 189 | + visibleEvent: 'onVisibleChange', | ||
| 190 | + }, | ||
| 191 | + ), | ||
| 192 | + ApiTreeSelect: withDefaultPlaceholder( | ||
| 193 | + { | ||
| 194 | + ...ApiComponent, | ||
| 195 | + name: 'ApiTreeSelect', | ||
| 196 | + }, | ||
| 197 | + 'select', | ||
| 198 | + { | ||
| 199 | + component: ElTreeSelect, | ||
| 200 | + props: { label: 'label', children: 'children' }, | ||
| 201 | + nodeKey: 'value', | ||
| 202 | + loadingSlot: 'loading', | ||
| 203 | + optionsPropName: 'data', | ||
| 204 | + visibleEvent: 'onVisibleChange', | ||
| 205 | + }, | ||
| 206 | + ), | ||
| 207 | + Checkbox: ElCheckbox, | ||
| 208 | + CheckboxGroup: (props, { attrs, slots }) => { | ||
| 209 | + let defaultSlot; | ||
| 210 | + if (Reflect.has(slots, 'default')) { | ||
| 211 | + defaultSlot = slots.default; | ||
| 212 | + } else { | ||
| 213 | + const { options, isButton } = attrs; | ||
| 214 | + if (Array.isArray(options)) { | ||
| 215 | + defaultSlot = () => | ||
| 216 | + options.map((option) => | ||
| 217 | + h(isButton ? ElCheckboxButton : ElCheckbox, option), | ||
| 218 | + ); | ||
| 219 | + } | ||
| 220 | + } | ||
| 221 | + return h( | ||
| 222 | + ElCheckboxGroup, | ||
| 223 | + { ...props, ...attrs }, | ||
| 224 | + { ...slots, default: defaultSlot }, | ||
| 225 | + ); | ||
| 226 | + }, | ||
| 227 | + // 自定义默认按钮 | ||
| 228 | + DefaultButton: (props, { attrs, slots }) => { | ||
| 229 | + return h(ElButton, { ...props, attrs, type: 'info' }, slots); | ||
| 230 | + }, | ||
| 231 | + // 自定义主要按钮 | ||
| 232 | + PrimaryButton: (props, { attrs, slots }) => { | ||
| 233 | + return h(ElButton, { ...props, attrs, type: 'primary' }, slots); | ||
| 234 | + }, | ||
| 235 | + Divider: ElDivider, | ||
| 236 | + IconPicker: withDefaultPlaceholder(IconPicker, 'select', { | ||
| 237 | + iconSlot: 'append', | ||
| 238 | + modelValueProp: 'model-value', | ||
| 239 | + inputComponent: ElInput, | ||
| 240 | + }), | ||
| 241 | + Input: withDefaultPlaceholder(ElInput, 'input'), | ||
| 242 | + InputNumber: withDefaultPlaceholder(ElInputNumber, 'input'), | ||
| 243 | + RadioGroup: (props, { attrs, slots }) => { | ||
| 244 | + let defaultSlot; | ||
| 245 | + if (Reflect.has(slots, 'default')) { | ||
| 246 | + defaultSlot = slots.default; | ||
| 247 | + } else { | ||
| 248 | + const { options } = attrs; | ||
| 249 | + if (Array.isArray(options)) { | ||
| 250 | + defaultSlot = () => | ||
| 251 | + options.map((option) => | ||
| 252 | + h(attrs.isButton ? ElRadioButton : ElRadio, option), | ||
| 253 | + ); | ||
| 254 | + } | ||
| 255 | + } | ||
| 256 | + return h( | ||
| 257 | + ElRadioGroup, | ||
| 258 | + { ...props, ...attrs }, | ||
| 259 | + { ...slots, default: defaultSlot }, | ||
| 260 | + ); | ||
| 261 | + }, | ||
| 262 | + Select: (props, { attrs, slots }) => { | ||
| 263 | + return h(ElSelectV2, { ...props, attrs }, slots); | ||
| 264 | + }, | ||
| 265 | + Space: ElSpace, | ||
| 266 | + Switch: ElSwitch, | ||
| 267 | + TimePicker: (props, { attrs, slots }) => { | ||
| 268 | + const { name, id, isRange } = props; | ||
| 269 | + const extraProps: Recordable<any> = {}; | ||
| 270 | + if (isRange) { | ||
| 271 | + if (name && !Array.isArray(name)) { | ||
| 272 | + extraProps.name = [name, `${name}_end`]; | ||
| 273 | + } | ||
| 274 | + if (id && !Array.isArray(id)) { | ||
| 275 | + extraProps.id = [id, `${id}_end`]; | ||
| 276 | + } | ||
| 277 | + } | ||
| 278 | + return h( | ||
| 279 | + ElTimePicker, | ||
| 280 | + { | ||
| 281 | + ...props, | ||
| 282 | + ...attrs, | ||
| 283 | + ...extraProps, | ||
| 284 | + }, | ||
| 285 | + slots, | ||
| 286 | + ); | ||
| 287 | + }, | ||
| 288 | + DatePicker: (props, { attrs, slots }) => { | ||
| 289 | + const { name, id, type } = props; | ||
| 290 | + const extraProps: Recordable<any> = {}; | ||
| 291 | + if (type && type.includes('range')) { | ||
| 292 | + if (name && !Array.isArray(name)) { | ||
| 293 | + extraProps.name = [name, `${name}_end`]; | ||
| 294 | + } | ||
| 295 | + if (id && !Array.isArray(id)) { | ||
| 296 | + extraProps.id = [id, `${id}_end`]; | ||
| 297 | + } | ||
| 298 | + } | ||
| 299 | + return h( | ||
| 300 | + ElDatePicker, | ||
| 301 | + { | ||
| 302 | + ...props, | ||
| 303 | + ...attrs, | ||
| 304 | + ...extraProps, | ||
| 305 | + }, | ||
| 306 | + slots, | ||
| 307 | + ); | ||
| 308 | + }, | ||
| 309 | + TreeSelect: withDefaultPlaceholder(ElTreeSelect, 'select'), | ||
| 310 | + Upload: ElUpload, | ||
| 311 | + }; | ||
| 312 | + | ||
| 313 | + // 将组件注册到全局共享状态中 | ||
| 314 | + globalShareState.setComponents(components); | ||
| 315 | + | ||
| 316 | + // 定义全局共享状态中的消息提示 | ||
| 317 | + globalShareState.defineMessage({ | ||
| 318 | + // 复制成功消息提示 | ||
| 319 | + copyPreferencesSuccess: (title, content) => { | ||
| 320 | + ElNotification({ | ||
| 321 | + title, | ||
| 322 | + message: content, | ||
| 323 | + position: 'bottom-right', | ||
| 324 | + duration: 0, | ||
| 325 | + type: 'success', | ||
| 326 | + }); | ||
| 327 | + }, | ||
| 328 | + }); | ||
| 329 | +} | ||
| 330 | + | ||
| 331 | +export { initComponentAdapter }; |
apps/web-payment/src/adapter/form.ts
0 → 100644
| 1 | +++ a/apps/web-payment/src/adapter/form.ts | ||
| 1 | +import type { | ||
| 2 | + VbenFormSchema as FormSchema, | ||
| 3 | + VbenFormProps, | ||
| 4 | +} from '@vben/common-ui'; | ||
| 5 | + | ||
| 6 | +import type { ComponentType } from './component'; | ||
| 7 | + | ||
| 8 | +import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui'; | ||
| 9 | +import { $t } from '@vben/locales'; | ||
| 10 | + | ||
| 11 | +async function initSetupVbenForm() { | ||
| 12 | + setupVbenForm<ComponentType>({ | ||
| 13 | + config: { | ||
| 14 | + modelPropNameMap: { | ||
| 15 | + Upload: 'fileList', | ||
| 16 | + CheckboxGroup: 'model-value', | ||
| 17 | + }, | ||
| 18 | + }, | ||
| 19 | + defineRules: { | ||
| 20 | + required: (value, _params, ctx) => { | ||
| 21 | + if (value === undefined || value === null || value.length === 0) { | ||
| 22 | + return $t('ui.formRules.required', [ctx.label]); | ||
| 23 | + } | ||
| 24 | + return true; | ||
| 25 | + }, | ||
| 26 | + selectRequired: (value, _params, ctx) => { | ||
| 27 | + if (value === undefined || value === null) { | ||
| 28 | + return $t('ui.formRules.selectRequired', [ctx.label]); | ||
| 29 | + } | ||
| 30 | + return true; | ||
| 31 | + }, | ||
| 32 | + }, | ||
| 33 | + }); | ||
| 34 | +} | ||
| 35 | + | ||
| 36 | +const useVbenForm = useForm<ComponentType>; | ||
| 37 | + | ||
| 38 | +export { initSetupVbenForm, useVbenForm, z }; | ||
| 39 | + | ||
| 40 | +export type VbenFormSchema = FormSchema<ComponentType>; | ||
| 41 | +export type { VbenFormProps }; |
apps/web-payment/src/adapter/vxe-table.ts
0 → 100644
| 1 | +++ a/apps/web-payment/src/adapter/vxe-table.ts | ||
| 1 | +import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; | ||
| 2 | + | ||
| 3 | +import { h } from 'vue'; | ||
| 4 | + | ||
| 5 | +import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table'; | ||
| 6 | + | ||
| 7 | +import { ElButton, ElImage } from 'element-plus'; | ||
| 8 | + | ||
| 9 | +import { useVbenForm } from './form'; | ||
| 10 | + | ||
| 11 | +setupVbenVxeTable({ | ||
| 12 | + configVxeTable: (vxeUI) => { | ||
| 13 | + vxeUI.setConfig({ | ||
| 14 | + grid: { | ||
| 15 | + align: 'center', | ||
| 16 | + border: false, | ||
| 17 | + columnConfig: { | ||
| 18 | + resizable: true, | ||
| 19 | + }, | ||
| 20 | + minHeight: 180, | ||
| 21 | + formConfig: { | ||
| 22 | + // 全局禁用vxe-table的表单配置,使用formOptions | ||
| 23 | + enabled: false, | ||
| 24 | + }, | ||
| 25 | + proxyConfig: { | ||
| 26 | + autoLoad: true, | ||
| 27 | + response: { | ||
| 28 | + result: 'items', | ||
| 29 | + total: 'total', | ||
| 30 | + list: 'items', | ||
| 31 | + }, | ||
| 32 | + showActiveMsg: true, | ||
| 33 | + showResponseMsg: false, | ||
| 34 | + }, | ||
| 35 | + round: true, | ||
| 36 | + showOverflow: true, | ||
| 37 | + size: 'small', | ||
| 38 | + } as VxeTableGridOptions, | ||
| 39 | + }); | ||
| 40 | + | ||
| 41 | + // 表格配置项可以用 cellRender: { name: 'CellImage' }, | ||
| 42 | + vxeUI.renderer.add('CellImage', { | ||
| 43 | + renderTableDefault(renderOpts, params) { | ||
| 44 | + const { props } = renderOpts; | ||
| 45 | + const { column, row } = params; | ||
| 46 | + const src = row[column.field]; | ||
| 47 | + return h(ElImage, { src, previewSrcList: [src], ...props }); | ||
| 48 | + }, | ||
| 49 | + }); | ||
| 50 | + | ||
| 51 | + // 表格配置项可以用 cellRender: { name: 'CellLink' }, | ||
| 52 | + vxeUI.renderer.add('CellLink', { | ||
| 53 | + renderTableDefault(renderOpts) { | ||
| 54 | + const { props } = renderOpts; | ||
| 55 | + return h( | ||
| 56 | + ElButton, | ||
| 57 | + { size: 'small', link: true }, | ||
| 58 | + { default: () => props?.text }, | ||
| 59 | + ); | ||
| 60 | + }, | ||
| 61 | + }); | ||
| 62 | + | ||
| 63 | + // 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化 | ||
| 64 | + // vxeUI.formats.add | ||
| 65 | + }, | ||
| 66 | + useVbenForm, | ||
| 67 | +}); | ||
| 68 | + | ||
| 69 | +export { useVbenVxeGrid }; | ||
| 70 | + | ||
| 71 | +export type * from '@vben/plugins/vxe-table'; |
apps/web-payment/src/api/core/auth.ts
0 → 100644
| 1 | +++ a/apps/web-payment/src/api/core/auth.ts | ||
| 1 | +import { baseRequestClient, requestClient } from '#/api/request'; | ||
| 2 | + | ||
| 3 | +export namespace AuthApi { | ||
| 4 | + /** 登录接口参数 */ | ||
| 5 | + export interface LoginParams { | ||
| 6 | + password?: string; | ||
| 7 | + username?: string; | ||
| 8 | + } | ||
| 9 | + | ||
| 10 | + /** 登录接口返回值 */ | ||
| 11 | + export interface LoginResult { | ||
| 12 | + accessToken: string; | ||
| 13 | + } | ||
| 14 | + | ||
| 15 | + export interface RefreshTokenResult { | ||
| 16 | + data: string; | ||
| 17 | + status: number; | ||
| 18 | + } | ||
| 19 | +} | ||
| 20 | + | ||
| 21 | +/** | ||
| 22 | + * 登录 | ||
| 23 | + */ | ||
| 24 | +export async function loginApi(data: AuthApi.LoginParams) { | ||
| 25 | + return requestClient.post<AuthApi.LoginResult>('/auth/login', data); | ||
| 26 | +} | ||
| 27 | + | ||
| 28 | +/** | ||
| 29 | + * 刷新accessToken | ||
| 30 | + */ | ||
| 31 | +export async function refreshTokenApi() { | ||
| 32 | + return baseRequestClient.post<AuthApi.RefreshTokenResult>('/auth/refresh', { | ||
| 33 | + withCredentials: true, | ||
| 34 | + }); | ||
| 35 | +} | ||
| 36 | + | ||
| 37 | +/** | ||
| 38 | + * 退出登录 | ||
| 39 | + */ | ||
| 40 | +export async function logoutApi() { | ||
| 41 | + return baseRequestClient.post('/auth/logout', { | ||
| 42 | + withCredentials: true, | ||
| 43 | + }); | ||
| 44 | +} | ||
| 45 | + | ||
| 46 | +/** | ||
| 47 | + * 获取用户权限码 | ||
| 48 | + */ | ||
| 49 | +export async function getAccessCodesApi() { | ||
| 50 | + return requestClient.get<string[]>('/auth/codes'); | ||
| 51 | +} |
apps/web-payment/src/api/core/index.ts
0 → 100644
apps/web-payment/src/api/core/menu.ts
0 → 100644
| 1 | +++ a/apps/web-payment/src/api/core/menu.ts | ||
| 1 | +import type { RouteRecordStringComponent } from '@vben/types'; | ||
| 2 | + | ||
| 3 | +import { requestClient } from '#/api/request'; | ||
| 4 | + | ||
| 5 | +/** | ||
| 6 | + * 获取用户所有菜单 | ||
| 7 | + */ | ||
| 8 | +export async function getAllMenusApi() { | ||
| 9 | + return requestClient.get<RouteRecordStringComponent[]>('/menu/all'); | ||
| 10 | +} |
apps/web-payment/src/api/core/user.ts
0 → 100644
apps/web-payment/src/api/index.ts
0 → 100644
apps/web-payment/src/api/request.ts
0 → 100644
| 1 | +++ a/apps/web-payment/src/api/request.ts | ||
| 1 | +/** | ||
| 2 | + * 该文件可自行根据业务逻辑进行调整 | ||
| 3 | + */ | ||
| 4 | +import type { RequestClientOptions } from '@vben/request'; | ||
| 5 | + | ||
| 6 | +import { useAppConfig } from '@vben/hooks'; | ||
| 7 | +import { preferences } from '@vben/preferences'; | ||
| 8 | +import { | ||
| 9 | + authenticateResponseInterceptor, | ||
| 10 | + defaultResponseInterceptor, | ||
| 11 | + errorMessageResponseInterceptor, | ||
| 12 | + RequestClient, | ||
| 13 | +} from '@vben/request'; | ||
| 14 | +import { useAccessStore } from '@vben/stores'; | ||
| 15 | + | ||
| 16 | +import { ElMessage } from 'element-plus'; | ||
| 17 | + | ||
| 18 | +import { useAuthStore } from '#/store'; | ||
| 19 | + | ||
| 20 | +import { refreshTokenApi } from './core'; | ||
| 21 | + | ||
| 22 | +const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD); | ||
| 23 | + | ||
| 24 | +function createRequestClient(baseURL: string, options?: RequestClientOptions) { | ||
| 25 | + const client = new RequestClient({ | ||
| 26 | + ...options, | ||
| 27 | + baseURL, | ||
| 28 | + }); | ||
| 29 | + | ||
| 30 | + /** | ||
| 31 | + * 重新认证逻辑 | ||
| 32 | + */ | ||
| 33 | + async function doReAuthenticate() { | ||
| 34 | + console.warn('Access token or refresh token is invalid or expired. '); | ||
| 35 | + const accessStore = useAccessStore(); | ||
| 36 | + const authStore = useAuthStore(); | ||
| 37 | + accessStore.setAccessToken(null); | ||
| 38 | + if ( | ||
| 39 | + preferences.app.loginExpiredMode === 'modal' && | ||
| 40 | + accessStore.isAccessChecked | ||
| 41 | + ) { | ||
| 42 | + accessStore.setLoginExpired(true); | ||
| 43 | + } else { | ||
| 44 | + await authStore.logout(); | ||
| 45 | + } | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + /** | ||
| 49 | + * 刷新token逻辑 | ||
| 50 | + */ | ||
| 51 | + async function doRefreshToken() { | ||
| 52 | + const accessStore = useAccessStore(); | ||
| 53 | + const resp = await refreshTokenApi(); | ||
| 54 | + const newToken = resp.data; | ||
| 55 | + accessStore.setAccessToken(newToken); | ||
| 56 | + return newToken; | ||
| 57 | + } | ||
| 58 | + | ||
| 59 | + function formatToken(token: null | string) { | ||
| 60 | + return token ? `Bearer ${token}` : null; | ||
| 61 | + } | ||
| 62 | + | ||
| 63 | + // 请求头处理 | ||
| 64 | + client.addRequestInterceptor({ | ||
| 65 | + fulfilled: async (config) => { | ||
| 66 | + const accessStore = useAccessStore(); | ||
| 67 | + | ||
| 68 | + config.headers.Authorization = formatToken(accessStore.accessToken); | ||
| 69 | + config.headers['Accept-Language'] = preferences.app.locale; | ||
| 70 | + return config; | ||
| 71 | + }, | ||
| 72 | + }); | ||
| 73 | + | ||
| 74 | + // 处理返回的响应数据格式 | ||
| 75 | + client.addResponseInterceptor( | ||
| 76 | + defaultResponseInterceptor({ | ||
| 77 | + codeField: 'code', | ||
| 78 | + dataField: 'data', | ||
| 79 | + successCode: 0, | ||
| 80 | + }), | ||
| 81 | + ); | ||
| 82 | + | ||
| 83 | + // token过期的处理 | ||
| 84 | + client.addResponseInterceptor( | ||
| 85 | + authenticateResponseInterceptor({ | ||
| 86 | + client, | ||
| 87 | + doReAuthenticate, | ||
| 88 | + doRefreshToken, | ||
| 89 | + enableRefreshToken: preferences.app.enableRefreshToken, | ||
| 90 | + formatToken, | ||
| 91 | + }), | ||
| 92 | + ); | ||
| 93 | + | ||
| 94 | + // 通用的错误处理,如果没有进入上面的错误处理逻辑,就会进入这里 | ||
| 95 | + client.addResponseInterceptor( | ||
| 96 | + errorMessageResponseInterceptor((msg: string, error) => { | ||
| 97 | + // 这里可以根据业务进行定制,你可以拿到 error 内的信息进行定制化处理,根据不同的 code 做不同的提示,而不是直接使用 message.error 提示 msg | ||
| 98 | + // 当前mock接口返回的错误字段是 error 或者 message | ||
| 99 | + const responseData = error?.response?.data ?? {}; | ||
| 100 | + const errorMessage = responseData?.error ?? responseData?.message ?? ''; | ||
| 101 | + // 如果没有错误信息,则会根据状态码进行提示 | ||
| 102 | + ElMessage.error(errorMessage || msg); | ||
| 103 | + }), | ||
| 104 | + ); | ||
| 105 | + | ||
| 106 | + return client; | ||
| 107 | +} | ||
| 108 | + | ||
| 109 | +export const requestClient = createRequestClient(apiURL, { | ||
| 110 | + responseReturn: 'data', | ||
| 111 | +}); | ||
| 112 | + | ||
| 113 | +export const baseRequestClient = new RequestClient({ baseURL: apiURL }); |
apps/web-payment/src/app.vue
0 → 100644
| 1 | +++ a/apps/web-payment/src/app.vue | ||
| 1 | +<script lang="ts" setup> | ||
| 2 | +import { useElementPlusDesignTokens } from '@vben/hooks'; | ||
| 3 | + | ||
| 4 | +import { ElConfigProvider } from 'element-plus'; | ||
| 5 | + | ||
| 6 | +import { elementLocale } from '#/locales'; | ||
| 7 | + | ||
| 8 | +defineOptions({ name: 'App' }); | ||
| 9 | + | ||
| 10 | +useElementPlusDesignTokens(); | ||
| 11 | +</script> | ||
| 12 | + | ||
| 13 | +<template> | ||
| 14 | + <ElConfigProvider :locale="elementLocale"> | ||
| 15 | + <RouterView /> | ||
| 16 | + </ElConfigProvider> | ||
| 17 | +</template> |
apps/web-payment/src/bootstrap.ts
0 → 100644
| 1 | +++ a/apps/web-payment/src/bootstrap.ts | ||
| 1 | +import { createApp, watchEffect } from 'vue'; | ||
| 2 | + | ||
| 3 | +import { registerAccessDirective } from '@vben/access'; | ||
| 4 | +import { registerLoadingDirective } from '@vben/common-ui'; | ||
| 5 | +import { preferences } from '@vben/preferences'; | ||
| 6 | +import { initStores } from '@vben/stores'; | ||
| 7 | +import '@vben/styles'; | ||
| 8 | +import '@vben/styles/ele'; | ||
| 9 | + | ||
| 10 | +import { useTitle } from '@vueuse/core'; | ||
| 11 | +import { ElLoading } from 'element-plus'; | ||
| 12 | + | ||
| 13 | +import { $t, setupI18n } from '#/locales'; | ||
| 14 | + | ||
| 15 | +import { initComponentAdapter } from './adapter/component'; | ||
| 16 | +import { initSetupVbenForm } from './adapter/form'; | ||
| 17 | +import App from './app.vue'; | ||
| 18 | +import { router } from './router'; | ||
| 19 | + | ||
| 20 | +async function bootstrap(namespace: string) { | ||
| 21 | + // 初始化组件适配器 | ||
| 22 | + await initComponentAdapter(); | ||
| 23 | + | ||
| 24 | + // 初始化表单组件 | ||
| 25 | + await initSetupVbenForm(); | ||
| 26 | + | ||
| 27 | + // // 设置弹窗的默认配置 | ||
| 28 | + // setDefaultModalProps({ | ||
| 29 | + // fullscreenButton: false, | ||
| 30 | + // }); | ||
| 31 | + // // 设置抽屉的默认配置 | ||
| 32 | + // setDefaultDrawerProps({ | ||
| 33 | + // zIndex: 2000, | ||
| 34 | + // }); | ||
| 35 | + const app = createApp(App); | ||
| 36 | + | ||
| 37 | + // 注册Element Plus提供的v-loading指令 | ||
| 38 | + app.directive('loading', ElLoading.directive); | ||
| 39 | + | ||
| 40 | + // 注册Vben提供的v-loading和v-spinning指令 | ||
| 41 | + registerLoadingDirective(app, { | ||
| 42 | + loading: false, // Vben提供的v-loading指令和Element Plus提供的v-loading指令二选一即可,此处false表示不注册Vben提供的v-loading指令 | ||
| 43 | + spinning: 'spinning', | ||
| 44 | + }); | ||
| 45 | + | ||
| 46 | + // 国际化 i18n 配置 | ||
| 47 | + await setupI18n(app); | ||
| 48 | + | ||
| 49 | + // 配置 pinia-tore | ||
| 50 | + await initStores(app, { namespace }); | ||
| 51 | + | ||
| 52 | + // 安装权限指令 | ||
| 53 | + registerAccessDirective(app); | ||
| 54 | + | ||
| 55 | + // 初始化 tippy | ||
| 56 | + const { initTippy } = await import('@vben/common-ui/es/tippy'); | ||
| 57 | + initTippy(app); | ||
| 58 | + | ||
| 59 | + // 配置路由及路由守卫 | ||
| 60 | + app.use(router); | ||
| 61 | + | ||
| 62 | + // 配置Motion插件 | ||
| 63 | + const { MotionPlugin } = await import('@vben/plugins/motion'); | ||
| 64 | + app.use(MotionPlugin); | ||
| 65 | + | ||
| 66 | + // 动态更新标题 | ||
| 67 | + watchEffect(() => { | ||
| 68 | + if (preferences.app.dynamicTitle) { | ||
| 69 | + const routeTitle = router.currentRoute.value.meta?.title; | ||
| 70 | + const pageTitle = | ||
| 71 | + (routeTitle ? `${$t(routeTitle)} - ` : '') + preferences.app.name; | ||
| 72 | + useTitle(pageTitle); | ||
| 73 | + } | ||
| 74 | + }); | ||
| 75 | + | ||
| 76 | + app.mount('#app'); | ||
| 77 | +} | ||
| 78 | + | ||
| 79 | +export { bootstrap }; |
apps/web-payment/src/layouts/auth.vue
0 → 100644
| 1 | +++ a/apps/web-payment/src/layouts/auth.vue | ||
| 1 | +<script lang="ts" setup> | ||
| 2 | +import { computed } from 'vue'; | ||
| 3 | + | ||
| 4 | +import { AuthPageLayout } from '@vben/layouts'; | ||
| 5 | +import { preferences } from '@vben/preferences'; | ||
| 6 | + | ||
| 7 | +import { $t } from '#/locales'; | ||
| 8 | + | ||
| 9 | +const appName = computed(() => preferences.app.name); | ||
| 10 | +const logo = computed(() => preferences.logo.source); | ||
| 11 | +const logoDark = computed(() => preferences.logo.sourceDark); | ||
| 12 | +</script> | ||
| 13 | + | ||
| 14 | +<template> | ||
| 15 | + <AuthPageLayout | ||
| 16 | + :app-name="appName" | ||
| 17 | + :logo="logo" | ||
| 18 | + :logo-dark="logoDark" | ||
| 19 | + :page-description="$t('authentication.pageDesc')" | ||
| 20 | + :page-title="$t('authentication.pageTitle')" | ||
| 21 | + > | ||
| 22 | + <!-- 自定义工具栏 --> | ||
| 23 | + <!-- <template #toolbar></template> --> | ||
| 24 | + </AuthPageLayout> | ||
| 25 | +</template> |
apps/web-payment/src/layouts/basic.vue
0 → 100644
| 1 | +++ a/apps/web-payment/src/layouts/basic.vue | ||
| 1 | +<script lang="ts" setup> | ||
| 2 | +import type { NotificationItem } from '@vben/layouts'; | ||
| 3 | + | ||
| 4 | +import { computed, ref, watch } from 'vue'; | ||
| 5 | +import { useRouter } from 'vue-router'; | ||
| 6 | + | ||
| 7 | +import { AuthenticationLoginExpiredModal } from '@vben/common-ui'; | ||
| 8 | +import { VBEN_DOC_URL, VBEN_GITHUB_URL } from '@vben/constants'; | ||
| 9 | +import { useWatermark } from '@vben/hooks'; | ||
| 10 | +import { BookOpenText, CircleHelp, SvgGithubIcon } from '@vben/icons'; | ||
| 11 | +import { | ||
| 12 | + BasicLayout, | ||
| 13 | + LockScreen, | ||
| 14 | + Notification, | ||
| 15 | + UserDropdown, | ||
| 16 | +} from '@vben/layouts'; | ||
| 17 | +import { preferences } from '@vben/preferences'; | ||
| 18 | +import { useAccessStore, useUserStore } from '@vben/stores'; | ||
| 19 | +import { openWindow } from '@vben/utils'; | ||
| 20 | + | ||
| 21 | +import { $t } from '#/locales'; | ||
| 22 | +import { useAuthStore } from '#/store'; | ||
| 23 | +import LoginForm from '#/views/_core/authentication/login.vue'; | ||
| 24 | + | ||
| 25 | +const notifications = ref<NotificationItem[]>([ | ||
| 26 | + { | ||
| 27 | + id: 1, | ||
| 28 | + avatar: 'https://avatar.vercel.sh/vercel.svg?text=VB', | ||
| 29 | + date: '3小时前', | ||
| 30 | + isRead: true, | ||
| 31 | + message: '描述信息描述信息描述信息', | ||
| 32 | + title: '收到了 14 份新周报', | ||
| 33 | + }, | ||
| 34 | + { | ||
| 35 | + id: 2, | ||
| 36 | + avatar: 'https://avatar.vercel.sh/1', | ||
| 37 | + date: '刚刚', | ||
| 38 | + isRead: false, | ||
| 39 | + message: '描述信息描述信息描述信息', | ||
| 40 | + title: '朱偏右 回复了你', | ||
| 41 | + }, | ||
| 42 | + { | ||
| 43 | + id: 3, | ||
| 44 | + avatar: 'https://avatar.vercel.sh/1', | ||
| 45 | + date: '2024-01-01', | ||
| 46 | + isRead: false, | ||
| 47 | + message: '描述信息描述信息描述信息', | ||
| 48 | + title: '曲丽丽 评论了你', | ||
| 49 | + }, | ||
| 50 | + { | ||
| 51 | + id: 4, | ||
| 52 | + avatar: 'https://avatar.vercel.sh/satori', | ||
| 53 | + date: '1天前', | ||
| 54 | + isRead: false, | ||
| 55 | + message: '描述信息描述信息描述信息', | ||
| 56 | + title: '代办提醒', | ||
| 57 | + }, | ||
| 58 | + { | ||
| 59 | + id: 5, | ||
| 60 | + avatar: 'https://avatar.vercel.sh/satori', | ||
| 61 | + date: '1天前', | ||
| 62 | + isRead: false, | ||
| 63 | + message: '描述信息描述信息描述信息', | ||
| 64 | + title: '跳转Workspace示例', | ||
| 65 | + link: '/workspace', | ||
| 66 | + }, | ||
| 67 | + { | ||
| 68 | + id: 6, | ||
| 69 | + avatar: 'https://avatar.vercel.sh/satori', | ||
| 70 | + date: '1天前', | ||
| 71 | + isRead: false, | ||
| 72 | + message: '描述信息描述信息描述信息', | ||
| 73 | + title: '跳转外部链接示例', | ||
| 74 | + link: 'https://doc.vben.pro', | ||
| 75 | + }, | ||
| 76 | +]); | ||
| 77 | + | ||
| 78 | +const router = useRouter(); | ||
| 79 | +const userStore = useUserStore(); | ||
| 80 | +const authStore = useAuthStore(); | ||
| 81 | +const accessStore = useAccessStore(); | ||
| 82 | +const { destroyWatermark, updateWatermark } = useWatermark(); | ||
| 83 | +const showDot = computed(() => | ||
| 84 | + notifications.value.some((item) => !item.isRead), | ||
| 85 | +); | ||
| 86 | + | ||
| 87 | +const menus = computed(() => [ | ||
| 88 | + { | ||
| 89 | + handler: () => { | ||
| 90 | + router.push({ name: 'Profile' }); | ||
| 91 | + }, | ||
| 92 | + icon: 'lucide:user', | ||
| 93 | + text: $t('page.auth.profile'), | ||
| 94 | + }, | ||
| 95 | + { | ||
| 96 | + handler: () => { | ||
| 97 | + openWindow(VBEN_DOC_URL, { | ||
| 98 | + target: '_blank', | ||
| 99 | + }); | ||
| 100 | + }, | ||
| 101 | + icon: BookOpenText, | ||
| 102 | + text: $t('ui.widgets.document'), | ||
| 103 | + }, | ||
| 104 | + { | ||
| 105 | + handler: () => { | ||
| 106 | + openWindow(VBEN_GITHUB_URL, { | ||
| 107 | + target: '_blank', | ||
| 108 | + }); | ||
| 109 | + }, | ||
| 110 | + icon: SvgGithubIcon, | ||
| 111 | + text: 'GitHub', | ||
| 112 | + }, | ||
| 113 | + { | ||
| 114 | + handler: () => { | ||
| 115 | + openWindow(`${VBEN_GITHUB_URL}/issues`, { | ||
| 116 | + target: '_blank', | ||
| 117 | + }); | ||
| 118 | + }, | ||
| 119 | + icon: CircleHelp, | ||
| 120 | + text: $t('ui.widgets.qa'), | ||
| 121 | + }, | ||
| 122 | +]); | ||
| 123 | + | ||
| 124 | +const avatar = computed(() => { | ||
| 125 | + return userStore.userInfo?.avatar ?? preferences.app.defaultAvatar; | ||
| 126 | +}); | ||
| 127 | + | ||
| 128 | +async function handleLogout() { | ||
| 129 | + await authStore.logout(false); | ||
| 130 | +} | ||
| 131 | + | ||
| 132 | +function handleNoticeClear() { | ||
| 133 | + notifications.value = []; | ||
| 134 | +} | ||
| 135 | + | ||
| 136 | +function markRead(id: number | string) { | ||
| 137 | + const item = notifications.value.find((item) => item.id === id); | ||
| 138 | + if (item) { | ||
| 139 | + item.isRead = true; | ||
| 140 | + } | ||
| 141 | +} | ||
| 142 | + | ||
| 143 | +function remove(id: number | string) { | ||
| 144 | + notifications.value = notifications.value.filter((item) => item.id !== id); | ||
| 145 | +} | ||
| 146 | + | ||
| 147 | +function handleMakeAll() { | ||
| 148 | + notifications.value.forEach((item) => (item.isRead = true)); | ||
| 149 | +} | ||
| 150 | +watch( | ||
| 151 | + () => ({ | ||
| 152 | + enable: preferences.app.watermark, | ||
| 153 | + content: preferences.app.watermarkContent, | ||
| 154 | + }), | ||
| 155 | + async ({ enable, content }) => { | ||
| 156 | + if (enable) { | ||
| 157 | + await updateWatermark({ | ||
| 158 | + content: | ||
| 159 | + content || | ||
| 160 | + `${userStore.userInfo?.username} - ${userStore.userInfo?.realName}`, | ||
| 161 | + }); | ||
| 162 | + } else { | ||
| 163 | + destroyWatermark(); | ||
| 164 | + } | ||
| 165 | + }, | ||
| 166 | + { | ||
| 167 | + immediate: true, | ||
| 168 | + }, | ||
| 169 | +); | ||
| 170 | +</script> | ||
| 171 | + | ||
| 172 | +<template> | ||
| 173 | + <BasicLayout @clear-preferences-and-logout="handleLogout"> | ||
| 174 | + <template #user-dropdown> | ||
| 175 | + <UserDropdown | ||
| 176 | + :avatar | ||
| 177 | + :menus | ||
| 178 | + :text="userStore.userInfo?.realName" | ||
| 179 | + description="ann.vben@gmail.com" | ||
| 180 | + tag-text="Pro" | ||
| 181 | + @logout="handleLogout" | ||
| 182 | + /> | ||
| 183 | + </template> | ||
| 184 | + <template #notification> | ||
| 185 | + <Notification | ||
| 186 | + :dot="showDot" | ||
| 187 | + :notifications="notifications" | ||
| 188 | + @clear="handleNoticeClear" | ||
| 189 | + @read="(item) => item.id && markRead(item.id)" | ||
| 190 | + @remove="(item) => item.id && remove(item.id)" | ||
| 191 | + @make-all="handleMakeAll" | ||
| 192 | + /> | ||
| 193 | + </template> | ||
| 194 | + <template #extra> | ||
| 195 | + <AuthenticationLoginExpiredModal | ||
| 196 | + v-model:open="accessStore.loginExpired" | ||
| 197 | + :avatar | ||
| 198 | + > | ||
| 199 | + <LoginForm /> | ||
| 200 | + </AuthenticationLoginExpiredModal> | ||
| 201 | + </template> | ||
| 202 | + <template #lock-screen> | ||
| 203 | + <LockScreen :avatar @to-login="handleLogout" /> | ||
| 204 | + </template> | ||
| 205 | + </BasicLayout> | ||
| 206 | +</template> |
apps/web-payment/src/layouts/index.ts
0 → 100644
apps/web-payment/src/locales/README.md
0 → 100644
apps/web-payment/src/locales/index.ts
0 → 100644
| 1 | +++ a/apps/web-payment/src/locales/index.ts | ||
| 1 | +import type { Language } from 'element-plus/es/locale'; | ||
| 2 | + | ||
| 3 | +import type { App } from 'vue'; | ||
| 4 | + | ||
| 5 | +import type { LocaleSetupOptions, SupportedLanguagesType } from '@vben/locales'; | ||
| 6 | + | ||
| 7 | +import { ref } from 'vue'; | ||
| 8 | + | ||
| 9 | +import { | ||
| 10 | + $t, | ||
| 11 | + setupI18n as coreSetup, | ||
| 12 | + loadLocalesMapFromDir, | ||
| 13 | +} from '@vben/locales'; | ||
| 14 | +import { preferences } from '@vben/preferences'; | ||
| 15 | + | ||
| 16 | +import dayjs from 'dayjs'; | ||
| 17 | +import enLocale from 'element-plus/es/locale/lang/en'; | ||
| 18 | +import defaultLocale from 'element-plus/es/locale/lang/zh-cn'; | ||
| 19 | + | ||
| 20 | +const elementLocale = ref<Language>(defaultLocale); | ||
| 21 | + | ||
| 22 | +const modules = import.meta.glob('./langs/**/*.json'); | ||
| 23 | + | ||
| 24 | +const localesMap = loadLocalesMapFromDir( | ||
| 25 | + /\.\/langs\/([^/]+)\/(.*)\.json$/, | ||
| 26 | + modules, | ||
| 27 | +); | ||
| 28 | +/** | ||
| 29 | + * 加载应用特有的语言包 | ||
| 30 | + * 这里也可以改造为从服务端获取翻译数据 | ||
| 31 | + * @param lang | ||
| 32 | + */ | ||
| 33 | +async function loadMessages(lang: SupportedLanguagesType) { | ||
| 34 | + const [appLocaleMessages] = await Promise.all([ | ||
| 35 | + localesMap[lang]?.(), | ||
| 36 | + loadThirdPartyMessage(lang), | ||
| 37 | + ]); | ||
| 38 | + return appLocaleMessages?.default; | ||
| 39 | +} | ||
| 40 | + | ||
| 41 | +/** | ||
| 42 | + * 加载第三方组件库的语言包 | ||
| 43 | + * @param lang | ||
| 44 | + */ | ||
| 45 | +async function loadThirdPartyMessage(lang: SupportedLanguagesType) { | ||
| 46 | + await Promise.all([loadElementLocale(lang), loadDayjsLocale(lang)]); | ||
| 47 | +} | ||
| 48 | + | ||
| 49 | +/** | ||
| 50 | + * 加载dayjs的语言包 | ||
| 51 | + * @param lang | ||
| 52 | + */ | ||
| 53 | +async function loadDayjsLocale(lang: SupportedLanguagesType) { | ||
| 54 | + let locale; | ||
| 55 | + switch (lang) { | ||
| 56 | + case 'en-US': { | ||
| 57 | + locale = await import('dayjs/locale/en'); | ||
| 58 | + break; | ||
| 59 | + } | ||
| 60 | + case 'zh-CN': { | ||
| 61 | + locale = await import('dayjs/locale/zh-cn'); | ||
| 62 | + break; | ||
| 63 | + } | ||
| 64 | + // 默认使用英语 | ||
| 65 | + default: { | ||
| 66 | + locale = await import('dayjs/locale/en'); | ||
| 67 | + } | ||
| 68 | + } | ||
| 69 | + if (locale) { | ||
| 70 | + dayjs.locale(locale); | ||
| 71 | + } else { | ||
| 72 | + console.error(`Failed to load dayjs locale for ${lang}`); | ||
| 73 | + } | ||
| 74 | +} | ||
| 75 | + | ||
| 76 | +/** | ||
| 77 | + * 加载element-plus的语言包 | ||
| 78 | + * @param lang | ||
| 79 | + */ | ||
| 80 | +async function loadElementLocale(lang: SupportedLanguagesType) { | ||
| 81 | + switch (lang) { | ||
| 82 | + case 'en-US': { | ||
| 83 | + elementLocale.value = enLocale; | ||
| 84 | + break; | ||
| 85 | + } | ||
| 86 | + case 'zh-CN': { | ||
| 87 | + elementLocale.value = defaultLocale; | ||
| 88 | + break; | ||
| 89 | + } | ||
| 90 | + } | ||
| 91 | +} | ||
| 92 | + | ||
| 93 | +async function setupI18n(app: App, options: LocaleSetupOptions = {}) { | ||
| 94 | + await coreSetup(app, { | ||
| 95 | + defaultLocale: preferences.app.locale, | ||
| 96 | + loadMessages, | ||
| 97 | + missingWarn: !import.meta.env.PROD, | ||
| 98 | + ...options, | ||
| 99 | + }); | ||
| 100 | +} | ||
| 101 | + | ||
| 102 | +export { $t, elementLocale, setupI18n }; |
apps/web-payment/src/locales/langs/en-US/demos.json
0 → 100644
| 1 | +++ a/apps/web-payment/src/locales/langs/en-US/demos.json | ||
| 1 | +{ | ||
| 2 | + "title": "Demos", | ||
| 3 | + "elementPlus": "Element Plus", | ||
| 4 | + "form": "Form", | ||
| 5 | + "vben": { | ||
| 6 | + "title": "Project", | ||
| 7 | + "about": "About", | ||
| 8 | + "document": "Document", | ||
| 9 | + "antdv": "Ant Design Vue Version", | ||
| 10 | + "naive-ui": "Naive UI Version", | ||
| 11 | + "element-plus": "Element Plus Version", | ||
| 12 | + "tdesign": "TDesign Vue Version" | ||
| 13 | + } | ||
| 14 | +} |
apps/web-payment/src/locales/langs/en-US/page.json
0 → 100644
| 1 | +++ a/apps/web-payment/src/locales/langs/en-US/page.json | ||
| 1 | +{ | ||
| 2 | + "auth": { | ||
| 3 | + "login": "Login", | ||
| 4 | + "register": "Register", | ||
| 5 | + "codeLogin": "Code Login", | ||
| 6 | + "qrcodeLogin": "Qr Code Login", | ||
| 7 | + "forgetPassword": "Forget Password", | ||
| 8 | + "profile": "Profile" | ||
| 9 | + }, | ||
| 10 | + "dashboard": { | ||
| 11 | + "title": "Dashboard", | ||
| 12 | + "analytics": "Analytics", | ||
| 13 | + "workspace": "Workspace" | ||
| 14 | + } | ||
| 15 | +} |
apps/web-payment/src/locales/langs/zh-CN/demos.json
0 → 100644
| 1 | +++ a/apps/web-payment/src/locales/langs/zh-CN/demos.json | ||
| 1 | +{ | ||
| 2 | + "title": "演示", | ||
| 3 | + "elementPlus": "Element Plus", | ||
| 4 | + "form": "表单演示", | ||
| 5 | + "vben": { | ||
| 6 | + "title": "项目", | ||
| 7 | + "about": "关于", | ||
| 8 | + "document": "文档", | ||
| 9 | + "antdv": "Ant Design Vue 版本", | ||
| 10 | + "naive-ui": "Naive UI 版本", | ||
| 11 | + "element-plus": "Element Plus 版本", | ||
| 12 | + "tdesign": "TDesign Vue 版本" | ||
| 13 | + } | ||
| 14 | +} |
apps/web-payment/src/locales/langs/zh-CN/page.json
0 → 100644
| 1 | +++ a/apps/web-payment/src/locales/langs/zh-CN/page.json | ||
| 1 | +{ | ||
| 2 | + "auth": { | ||
| 3 | + "login": "登录", | ||
| 4 | + "register": "注册", | ||
| 5 | + "codeLogin": "验证码登录", | ||
| 6 | + "qrcodeLogin": "二维码登录", | ||
| 7 | + "forgetPassword": "忘记密码", | ||
| 8 | + "profile": "个人中心" | ||
| 9 | + }, | ||
| 10 | + "dashboard": { | ||
| 11 | + "title": "概览", | ||
| 12 | + "analytics": "分析页", | ||
| 13 | + "workspace": "工作台" | ||
| 14 | + } | ||
| 15 | +} |
apps/web-payment/src/main.ts
0 → 100644
| 1 | +++ a/apps/web-payment/src/main.ts | ||
| 1 | +import { initPreferences } from '@vben/preferences'; | ||
| 2 | +import { unmountGlobalLoading } from '@vben/utils'; | ||
| 3 | + | ||
| 4 | +import { overridesPreferences } from './preferences'; | ||
| 5 | + | ||
| 6 | +/** | ||
| 7 | + * 应用初始化完成之后再进行页面加载渲染 | ||
| 8 | + */ | ||
| 9 | +async function initApplication() { | ||
| 10 | + // name用于指定项目唯一标识 | ||
| 11 | + // 用于区分不同项目的偏好设置以及存储数据的key前缀以及其他一些需要隔离的数据 | ||
| 12 | + const env = import.meta.env.PROD ? 'prod' : 'dev'; | ||
| 13 | + const appVersion = import.meta.env.VITE_APP_VERSION; | ||
| 14 | + const namespace = `${import.meta.env.VITE_APP_NAMESPACE}-${appVersion}-${env}`; | ||
| 15 | + | ||
| 16 | + // app偏好设置初始化 | ||
| 17 | + await initPreferences({ | ||
| 18 | + namespace, | ||
| 19 | + overrides: overridesPreferences, | ||
| 20 | + }); | ||
| 21 | + | ||
| 22 | + // 启动应用并挂载 | ||
| 23 | + // vue应用主要逻辑及视图 | ||
| 24 | + const { bootstrap } = await import('./bootstrap'); | ||
| 25 | + await bootstrap(namespace); | ||
| 26 | + | ||
| 27 | + // 移除并销毁loading | ||
| 28 | + unmountGlobalLoading(); | ||
| 29 | +} | ||
| 30 | + | ||
| 31 | +initApplication(); |
apps/web-payment/src/preferences.ts
0 → 100644
| 1 | +++ a/apps/web-payment/src/preferences.ts | ||
| 1 | +import { defineOverridesPreferences } from '@vben/preferences'; | ||
| 2 | + | ||
| 3 | +/** | ||
| 4 | + * @description 项目配置文件 | ||
| 5 | + * 只需要覆盖项目中的一部分配置,不需要的配置不用覆盖,会自动使用默认配置 | ||
| 6 | + * !!! 更改配置后请清空缓存,否则可能不生效 | ||
| 7 | + */ | ||
| 8 | +export const overridesPreferences = defineOverridesPreferences({ | ||
| 9 | + // overrides | ||
| 10 | + app: { | ||
| 11 | + name: import.meta.env.VITE_APP_TITLE, | ||
| 12 | + defaultHomePath: '/payment', | ||
| 13 | + }, | ||
| 14 | + theme: { | ||
| 15 | + mode: 'light', | ||
| 16 | + }, | ||
| 17 | +}); |
apps/web-payment/src/router/access.ts
0 → 100644
| 1 | +++ a/apps/web-payment/src/router/access.ts | ||
| 1 | +import type { | ||
| 2 | + ComponentRecordType, | ||
| 3 | + GenerateMenuAndRoutesOptions, | ||
| 4 | +} from '@vben/types'; | ||
| 5 | + | ||
| 6 | +import { generateAccessible } from '@vben/access'; | ||
| 7 | +import { preferences } from '@vben/preferences'; | ||
| 8 | + | ||
| 9 | +import { ElMessage } from 'element-plus'; | ||
| 10 | + | ||
| 11 | +import { getAllMenusApi } from '#/api'; | ||
| 12 | +import { BasicLayout, IFrameView } from '#/layouts'; | ||
| 13 | +import { $t } from '#/locales'; | ||
| 14 | + | ||
| 15 | +const forbiddenComponent = () => import('#/views/_core/fallback/forbidden.vue'); | ||
| 16 | + | ||
| 17 | +async function generateAccess(options: GenerateMenuAndRoutesOptions) { | ||
| 18 | + const pageMap: ComponentRecordType = import.meta.glob('../views/**/*.vue'); | ||
| 19 | + | ||
| 20 | + const layoutMap: ComponentRecordType = { | ||
| 21 | + BasicLayout, | ||
| 22 | + IFrameView, | ||
| 23 | + }; | ||
| 24 | + | ||
| 25 | + return await generateAccessible(preferences.app.accessMode, { | ||
| 26 | + ...options, | ||
| 27 | + fetchMenuListAsync: async () => { | ||
| 28 | + ElMessage({ | ||
| 29 | + duration: 1500, | ||
| 30 | + message: `${$t('common.loadingMenu')}...`, | ||
| 31 | + }); | ||
| 32 | + return await getAllMenusApi(); | ||
| 33 | + }, | ||
| 34 | + // 可以指定没有权限跳转403页面 | ||
| 35 | + forbiddenComponent, | ||
| 36 | + // 如果 route.meta.menuVisibleWithForbidden = true | ||
| 37 | + layoutMap, | ||
| 38 | + pageMap, | ||
| 39 | + }); | ||
| 40 | +} | ||
| 41 | + | ||
| 42 | +export { generateAccess }; |
apps/web-payment/src/router/guard.ts
0 → 100644
| 1 | +++ a/apps/web-payment/src/router/guard.ts | ||
| 1 | +import type { Router } from 'vue-router'; | ||
| 2 | + | ||
| 3 | +import { preferences } from '@vben/preferences'; | ||
| 4 | +import { startProgress, stopProgress } from '@vben/utils'; | ||
| 5 | + | ||
| 6 | +/** | ||
| 7 | + * 通用守卫配置 | ||
| 8 | + * @param router | ||
| 9 | + */ | ||
| 10 | +function setupCommonGuard(router: Router) { | ||
| 11 | + // 记录已经加载的页面 | ||
| 12 | + const loadedPaths = new Set<string>(); | ||
| 13 | + | ||
| 14 | + router.beforeEach((to) => { | ||
| 15 | + to.meta.loaded = loadedPaths.has(to.path); | ||
| 16 | + | ||
| 17 | + // 页面加载进度条 | ||
| 18 | + if (!to.meta.loaded && preferences.transition.progress) { | ||
| 19 | + startProgress(); | ||
| 20 | + } | ||
| 21 | + return true; | ||
| 22 | + }); | ||
| 23 | + | ||
| 24 | + router.afterEach((to) => { | ||
| 25 | + // 记录页面是否加载,如果已经加载,后续的页面切换动画等效果不在重复执行 | ||
| 26 | + | ||
| 27 | + loadedPaths.add(to.path); | ||
| 28 | + | ||
| 29 | + // 关闭页面加载进度条 | ||
| 30 | + if (preferences.transition.progress) { | ||
| 31 | + stopProgress(); | ||
| 32 | + } | ||
| 33 | + }); | ||
| 34 | +} | ||
| 35 | + | ||
| 36 | +/** | ||
| 37 | + * 权限访问守卫配置 — 已禁用权限校验, 所有路由可直接访问 | ||
| 38 | + * @param router | ||
| 39 | + */ | ||
| 40 | +function setupAccessGuard(router: Router) { | ||
| 41 | + // 直接放行所有路由,跳过权限校验 | ||
| 42 | + router.beforeEach(() => { | ||
| 43 | + return true; | ||
| 44 | + }); | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +/** | ||
| 48 | + * 项目守卫配置 | ||
| 49 | + * @param router | ||
| 50 | + */ | ||
| 51 | +function createRouterGuard(router: Router) { | ||
| 52 | + /** 通用 */ | ||
| 53 | + setupCommonGuard(router); | ||
| 54 | + /** 权限访问 */ | ||
| 55 | + setupAccessGuard(router); | ||
| 56 | +} | ||
| 57 | + | ||
| 58 | +export { createRouterGuard }; |
apps/web-payment/src/router/index.ts
0 → 100644
| 1 | +++ a/apps/web-payment/src/router/index.ts | ||
| 1 | +import { | ||
| 2 | + createRouter, | ||
| 3 | + createWebHashHistory, | ||
| 4 | + createWebHistory, | ||
| 5 | +} from 'vue-router'; | ||
| 6 | + | ||
| 7 | +import { resetStaticRoutes } from '@vben/utils'; | ||
| 8 | + | ||
| 9 | +import { createRouterGuard } from './guard'; | ||
| 10 | +import { routes } from './routes'; | ||
| 11 | + | ||
| 12 | +/** | ||
| 13 | + * @zh_CN 创建vue-router实例 | ||
| 14 | + */ | ||
| 15 | +const router = createRouter({ | ||
| 16 | + history: | ||
| 17 | + import.meta.env.VITE_ROUTER_HISTORY === 'hash' | ||
| 18 | + ? createWebHashHistory(import.meta.env.VITE_BASE) | ||
| 19 | + : createWebHistory(import.meta.env.VITE_BASE), | ||
| 20 | + // 应该添加到路由的初始路由列表。 | ||
| 21 | + routes, | ||
| 22 | + scrollBehavior: (to, _from, savedPosition) => { | ||
| 23 | + if (savedPosition) { | ||
| 24 | + return savedPosition; | ||
| 25 | + } | ||
| 26 | + return to.hash ? { behavior: 'smooth', el: to.hash } : { left: 0, top: 0 }; | ||
| 27 | + }, | ||
| 28 | + // 是否应该禁止尾部斜杠。 | ||
| 29 | + // strict: true, | ||
| 30 | +}); | ||
| 31 | + | ||
| 32 | +const resetRoutes = () => resetStaticRoutes(router, routes); | ||
| 33 | + | ||
| 34 | +// 创建路由守卫 | ||
| 35 | +createRouterGuard(router); | ||
| 36 | + | ||
| 37 | +export { resetRoutes, router }; |
apps/web-payment/src/router/routes/core.ts
0 → 100644
| 1 | +++ a/apps/web-payment/src/router/routes/core.ts | ||
| 1 | +import type { RouteRecordRaw } from 'vue-router'; | ||
| 2 | + | ||
| 3 | +import { preferences } from '@vben/preferences'; | ||
| 4 | + | ||
| 5 | +const BasicLayout = () => import('#/layouts/basic.vue'); | ||
| 6 | +/** 全局404页面 */ | ||
| 7 | +const fallbackNotFoundRoute: RouteRecordRaw = { | ||
| 8 | + component: () => import('#/views/_core/fallback/not-found.vue'), | ||
| 9 | + meta: { | ||
| 10 | + hideInBreadcrumb: true, | ||
| 11 | + hideInMenu: true, | ||
| 12 | + hideInTab: true, | ||
| 13 | + title: '404', | ||
| 14 | + }, | ||
| 15 | + name: 'FallbackNotFound', | ||
| 16 | + path: '/:path(.*)*', | ||
| 17 | +}; | ||
| 18 | + | ||
| 19 | +/** 基本路由,这些路由是必须存在的 */ | ||
| 20 | +const coreRoutes: RouteRecordRaw[] = [ | ||
| 21 | + /** | ||
| 22 | + * 根路由 | ||
| 23 | + * 使用基础布局,作为所有页面的父级容器,子级就不必配置BasicLayout。 | ||
| 24 | + * 此路由必须存在,且不应修改 | ||
| 25 | + */ | ||
| 26 | + { | ||
| 27 | + component: BasicLayout, | ||
| 28 | + meta: { | ||
| 29 | + hideInBreadcrumb: true, | ||
| 30 | + title: 'Root', | ||
| 31 | + }, | ||
| 32 | + name: 'Root', | ||
| 33 | + path: '/', | ||
| 34 | + redirect: preferences.app.defaultHomePath, | ||
| 35 | + children: [], | ||
| 36 | + }, | ||
| 37 | + { | ||
| 38 | + name: 'Payment', | ||
| 39 | + path: '/payment', | ||
| 40 | + component: () => import('#/views/payment/index.vue'), | ||
| 41 | + meta: { | ||
| 42 | + icon: 'lucide:area-chart', | ||
| 43 | + title: '收银台', | ||
| 44 | + }, | ||
| 45 | + }, | ||
| 46 | +]; | ||
| 47 | + | ||
| 48 | +export { coreRoutes, fallbackNotFoundRoute }; |
apps/web-payment/src/router/routes/index.ts
0 → 100644
| 1 | +++ a/apps/web-payment/src/router/routes/index.ts | ||
| 1 | +import type { RouteRecordRaw } from 'vue-router'; | ||
| 2 | + | ||
| 3 | +import { mergeRouteModules, traverseTreeValues } from '@vben/utils'; | ||
| 4 | + | ||
| 5 | +import { coreRoutes, fallbackNotFoundRoute } from './core'; | ||
| 6 | + | ||
| 7 | +const dynamicRouteFiles = import.meta.glob('./modules/**/*.ts', { | ||
| 8 | + eager: true, | ||
| 9 | +}); | ||
| 10 | + | ||
| 11 | +// 有需要可以自行打开注释,并创建文件夹 | ||
| 12 | +// const externalRouteFiles = import.meta.glob('./external/**/*.ts', { eager: true }); | ||
| 13 | +// const staticRouteFiles = import.meta.glob('./static/**/*.ts', { eager: true }); | ||
| 14 | + | ||
| 15 | +/** 动态路由 */ | ||
| 16 | +const dynamicRoutes: RouteRecordRaw[] = mergeRouteModules(dynamicRouteFiles); | ||
| 17 | + | ||
| 18 | +/** 外部路由列表,访问这些页面可以不需要Layout,可能用于内嵌在别的系统(不会显示在菜单中) */ | ||
| 19 | +// const externalRoutes: RouteRecordRaw[] = mergeRouteModules(externalRouteFiles); | ||
| 20 | +// const staticRoutes: RouteRecordRaw[] = mergeRouteModules(staticRouteFiles); | ||
| 21 | +const staticRoutes: RouteRecordRaw[] = []; | ||
| 22 | +const externalRoutes: RouteRecordRaw[] = []; | ||
| 23 | + | ||
| 24 | +/** 路由列表,由基本路由、外部路由和404兜底路由组成 | ||
| 25 | + * 无需走权限验证(会一直显示在菜单中) */ | ||
| 26 | +const routes: RouteRecordRaw[] = [ | ||
| 27 | + ...coreRoutes, | ||
| 28 | + ...externalRoutes, | ||
| 29 | + fallbackNotFoundRoute, | ||
| 30 | +]; | ||
| 31 | + | ||
| 32 | +/** 基本路由列表,这些路由不需要进入权限拦截 */ | ||
| 33 | +const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name); | ||
| 34 | + | ||
| 35 | +/** 有权限校验的路由列表,包含动态路由和静态路由 */ | ||
| 36 | +const accessRoutes = [...dynamicRoutes, ...staticRoutes]; | ||
| 37 | +export { accessRoutes, coreRouteNames, routes }; |
apps/web-payment/src/store/auth.ts
0 → 100644
| 1 | +++ a/apps/web-payment/src/store/auth.ts | ||
| 1 | +import type { Recordable, UserInfo } from '@vben/types'; | ||
| 2 | + | ||
| 3 | +import { ref } from 'vue'; | ||
| 4 | +import { useRouter } from 'vue-router'; | ||
| 5 | + | ||
| 6 | +import { LOGIN_PATH } from '@vben/constants'; | ||
| 7 | +import { preferences } from '@vben/preferences'; | ||
| 8 | +import { resetAllStores, useAccessStore, useUserStore } from '@vben/stores'; | ||
| 9 | + | ||
| 10 | +import { ElNotification } from 'element-plus'; | ||
| 11 | +import { defineStore } from 'pinia'; | ||
| 12 | + | ||
| 13 | +import { getAccessCodesApi, getUserInfoApi, loginApi, logoutApi } from '#/api'; | ||
| 14 | +import { $t } from '#/locales'; | ||
| 15 | + | ||
| 16 | +export const useAuthStore = defineStore('auth', () => { | ||
| 17 | + const accessStore = useAccessStore(); | ||
| 18 | + const userStore = useUserStore(); | ||
| 19 | + const router = useRouter(); | ||
| 20 | + | ||
| 21 | + const loginLoading = ref(false); | ||
| 22 | + | ||
| 23 | + /** | ||
| 24 | + * 异步处理登录操作 | ||
| 25 | + * Asynchronously handle the login process | ||
| 26 | + * @param params 登录表单数据 | ||
| 27 | + */ | ||
| 28 | + async function authLogin( | ||
| 29 | + params: Recordable<any>, | ||
| 30 | + onSuccess?: () => Promise<void> | void, | ||
| 31 | + ) { | ||
| 32 | + // 异步处理用户登录操作并获取 accessToken | ||
| 33 | + let userInfo: null | UserInfo = null; | ||
| 34 | + try { | ||
| 35 | + loginLoading.value = true; | ||
| 36 | + const { accessToken } = await loginApi(params); | ||
| 37 | + | ||
| 38 | + // 如果成功获取到 accessToken | ||
| 39 | + if (accessToken) { | ||
| 40 | + // 将 accessToken 存储到 accessStore 中 | ||
| 41 | + accessStore.setAccessToken(accessToken); | ||
| 42 | + | ||
| 43 | + // 获取用户信息并存储到 accessStore 中 | ||
| 44 | + const [fetchUserInfoResult, accessCodes] = await Promise.all([ | ||
| 45 | + fetchUserInfo(), | ||
| 46 | + getAccessCodesApi(), | ||
| 47 | + ]); | ||
| 48 | + | ||
| 49 | + userInfo = fetchUserInfoResult; | ||
| 50 | + | ||
| 51 | + userStore.setUserInfo(userInfo); | ||
| 52 | + accessStore.setAccessCodes(accessCodes); | ||
| 53 | + | ||
| 54 | + if (accessStore.loginExpired) { | ||
| 55 | + accessStore.setLoginExpired(false); | ||
| 56 | + } else { | ||
| 57 | + onSuccess | ||
| 58 | + ? await onSuccess?.() | ||
| 59 | + : await router.push( | ||
| 60 | + userInfo.homePath || preferences.app.defaultHomePath, | ||
| 61 | + ); | ||
| 62 | + } | ||
| 63 | + | ||
| 64 | + if (userInfo?.realName) { | ||
| 65 | + ElNotification({ | ||
| 66 | + message: `${$t('authentication.loginSuccessDesc')}:${userInfo?.realName}`, | ||
| 67 | + title: $t('authentication.loginSuccess'), | ||
| 68 | + type: 'success', | ||
| 69 | + }); | ||
| 70 | + } | ||
| 71 | + } | ||
| 72 | + } finally { | ||
| 73 | + loginLoading.value = false; | ||
| 74 | + } | ||
| 75 | + | ||
| 76 | + return { | ||
| 77 | + userInfo, | ||
| 78 | + }; | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + async function logout(redirect: boolean = true) { | ||
| 82 | + try { | ||
| 83 | + await logoutApi(); | ||
| 84 | + } catch { | ||
| 85 | + // 不做任何处理 | ||
| 86 | + } | ||
| 87 | + resetAllStores(); | ||
| 88 | + accessStore.setLoginExpired(false); | ||
| 89 | + | ||
| 90 | + // 回登录页带上当前路由地址 | ||
| 91 | + await router.replace({ | ||
| 92 | + path: LOGIN_PATH, | ||
| 93 | + query: redirect | ||
| 94 | + ? { | ||
| 95 | + redirect: encodeURIComponent(router.currentRoute.value.fullPath), | ||
| 96 | + } | ||
| 97 | + : {}, | ||
| 98 | + }); | ||
| 99 | + } | ||
| 100 | + | ||
| 101 | + async function fetchUserInfo() { | ||
| 102 | + let userInfo: null | UserInfo = null; | ||
| 103 | + userInfo = await getUserInfoApi(); | ||
| 104 | + userStore.setUserInfo(userInfo); | ||
| 105 | + return userInfo; | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + function $reset() { | ||
| 109 | + loginLoading.value = false; | ||
| 110 | + } | ||
| 111 | + | ||
| 112 | + return { | ||
| 113 | + $reset, | ||
| 114 | + authLogin, | ||
| 115 | + fetchUserInfo, | ||
| 116 | + loginLoading, | ||
| 117 | + logout, | ||
| 118 | + }; | ||
| 119 | +}); |
apps/web-payment/src/store/index.ts
0 → 100644
apps/web-payment/src/views/_core/README.md
0 → 100644
apps/web-payment/src/views/_core/about/index.vue
0 → 100644
apps/web-payment/src/views/_core/authentication/code-login.vue
0 → 100644
| 1 | +++ a/apps/web-payment/src/views/_core/authentication/code-login.vue | ||
| 1 | +<script lang="ts" setup> | ||
| 2 | +import type { VbenFormSchema } from '@vben/common-ui'; | ||
| 3 | +import type { Recordable } from '@vben/types'; | ||
| 4 | + | ||
| 5 | +import { computed, ref } from 'vue'; | ||
| 6 | + | ||
| 7 | +import { AuthenticationCodeLogin, z } from '@vben/common-ui'; | ||
| 8 | +import { $t } from '@vben/locales'; | ||
| 9 | + | ||
| 10 | +defineOptions({ name: 'CodeLogin' }); | ||
| 11 | + | ||
| 12 | +const loading = ref(false); | ||
| 13 | +const CODE_LENGTH = 6; | ||
| 14 | + | ||
| 15 | +const formSchema = computed((): VbenFormSchema[] => { | ||
| 16 | + return [ | ||
| 17 | + { | ||
| 18 | + component: 'VbenInput', | ||
| 19 | + componentProps: { | ||
| 20 | + placeholder: $t('authentication.mobile'), | ||
| 21 | + }, | ||
| 22 | + fieldName: 'phoneNumber', | ||
| 23 | + label: $t('authentication.mobile'), | ||
| 24 | + rules: z | ||
| 25 | + .string() | ||
| 26 | + .min(1, { message: $t('authentication.mobileTip') }) | ||
| 27 | + .refine((v) => /^\d{11}$/.test(v), { | ||
| 28 | + message: $t('authentication.mobileErrortip'), | ||
| 29 | + }), | ||
| 30 | + }, | ||
| 31 | + { | ||
| 32 | + component: 'VbenPinInput', | ||
| 33 | + componentProps: { | ||
| 34 | + codeLength: CODE_LENGTH, | ||
| 35 | + createText: (countdown: number) => { | ||
| 36 | + const text = | ||
| 37 | + countdown > 0 | ||
| 38 | + ? $t('authentication.sendText', [countdown]) | ||
| 39 | + : $t('authentication.sendCode'); | ||
| 40 | + return text; | ||
| 41 | + }, | ||
| 42 | + placeholder: $t('authentication.code'), | ||
| 43 | + }, | ||
| 44 | + fieldName: 'code', | ||
| 45 | + label: $t('authentication.code'), | ||
| 46 | + rules: z.string().length(CODE_LENGTH, { | ||
| 47 | + message: $t('authentication.codeTip', [CODE_LENGTH]), | ||
| 48 | + }), | ||
| 49 | + }, | ||
| 50 | + ]; | ||
| 51 | +}); | ||
| 52 | +/** | ||
| 53 | + * 异步处理登录操作 | ||
| 54 | + * Asynchronously handle the login process | ||
| 55 | + * @param values 登录表单数据 | ||
| 56 | + */ | ||
| 57 | +async function handleLogin(values: Recordable<any>) { | ||
| 58 | + // eslint-disable-next-line no-console | ||
| 59 | + console.log(values); | ||
| 60 | +} | ||
| 61 | +</script> | ||
| 62 | + | ||
| 63 | +<template> | ||
| 64 | + <AuthenticationCodeLogin | ||
| 65 | + :form-schema="formSchema" | ||
| 66 | + :loading="loading" | ||
| 67 | + @submit="handleLogin" | ||
| 68 | + /> | ||
| 69 | +</template> |
apps/web-payment/src/views/_core/authentication/forget-password.vue
0 → 100644
| 1 | +++ a/apps/web-payment/src/views/_core/authentication/forget-password.vue | ||
| 1 | +<script lang="ts" setup> | ||
| 2 | +import type { VbenFormSchema } from '@vben/common-ui'; | ||
| 3 | +import type { Recordable } from '@vben/types'; | ||
| 4 | + | ||
| 5 | +import { computed, ref } from 'vue'; | ||
| 6 | + | ||
| 7 | +import { AuthenticationForgetPassword, z } from '@vben/common-ui'; | ||
| 8 | +import { $t } from '@vben/locales'; | ||
| 9 | + | ||
| 10 | +defineOptions({ name: 'ForgetPassword' }); | ||
| 11 | + | ||
| 12 | +const loading = ref(false); | ||
| 13 | + | ||
| 14 | +const formSchema = computed((): VbenFormSchema[] => { | ||
| 15 | + return [ | ||
| 16 | + { | ||
| 17 | + component: 'VbenInput', | ||
| 18 | + componentProps: { | ||
| 19 | + placeholder: 'example@example.com', | ||
| 20 | + }, | ||
| 21 | + fieldName: 'email', | ||
| 22 | + label: $t('authentication.email'), | ||
| 23 | + rules: z | ||
| 24 | + .string() | ||
| 25 | + .min(1, { message: $t('authentication.emailTip') }) | ||
| 26 | + .email($t('authentication.emailValidErrorTip')), | ||
| 27 | + }, | ||
| 28 | + ]; | ||
| 29 | +}); | ||
| 30 | + | ||
| 31 | +function handleSubmit(value: Recordable<any>) { | ||
| 32 | + // eslint-disable-next-line no-console | ||
| 33 | + console.log('reset email:', value); | ||
| 34 | +} | ||
| 35 | +</script> | ||
| 36 | + | ||
| 37 | +<template> | ||
| 38 | + <AuthenticationForgetPassword | ||
| 39 | + :form-schema="formSchema" | ||
| 40 | + :loading="loading" | ||
| 41 | + @submit="handleSubmit" | ||
| 42 | + /> | ||
| 43 | +</template> |
apps/web-payment/src/views/_core/authentication/login.vue
0 → 100644
| 1 | +++ a/apps/web-payment/src/views/_core/authentication/login.vue | ||
| 1 | +<script lang="ts" setup> | ||
| 2 | +import type { VbenFormSchema } from '@vben/common-ui'; | ||
| 3 | +import type { BasicOption } from '@vben/types'; | ||
| 4 | + | ||
| 5 | +import { computed, markRaw } from 'vue'; | ||
| 6 | + | ||
| 7 | +import { AuthenticationLogin, SliderCaptcha, z } from '@vben/common-ui'; | ||
| 8 | +import { $t } from '@vben/locales'; | ||
| 9 | + | ||
| 10 | +import { useAuthStore } from '#/store'; | ||
| 11 | + | ||
| 12 | +defineOptions({ name: 'Login' }); | ||
| 13 | + | ||
| 14 | +const authStore = useAuthStore(); | ||
| 15 | + | ||
| 16 | +const MOCK_USER_OPTIONS: BasicOption[] = [ | ||
| 17 | + { | ||
| 18 | + label: 'Super', | ||
| 19 | + value: 'vben', | ||
| 20 | + }, | ||
| 21 | + { | ||
| 22 | + label: 'Admin', | ||
| 23 | + value: 'admin', | ||
| 24 | + }, | ||
| 25 | + { | ||
| 26 | + label: 'User', | ||
| 27 | + value: 'jack', | ||
| 28 | + }, | ||
| 29 | +]; | ||
| 30 | + | ||
| 31 | +const formSchema = computed((): VbenFormSchema[] => { | ||
| 32 | + return [ | ||
| 33 | + { | ||
| 34 | + component: 'VbenSelect', | ||
| 35 | + componentProps: { | ||
| 36 | + options: MOCK_USER_OPTIONS, | ||
| 37 | + placeholder: $t('authentication.selectAccount'), | ||
| 38 | + }, | ||
| 39 | + fieldName: 'selectAccount', | ||
| 40 | + label: $t('authentication.selectAccount'), | ||
| 41 | + rules: z | ||
| 42 | + .string() | ||
| 43 | + .min(1, { message: $t('authentication.selectAccount') }) | ||
| 44 | + .optional() | ||
| 45 | + .default('vben'), | ||
| 46 | + }, | ||
| 47 | + { | ||
| 48 | + component: 'VbenInput', | ||
| 49 | + componentProps: { | ||
| 50 | + placeholder: $t('authentication.usernameTip'), | ||
| 51 | + }, | ||
| 52 | + dependencies: { | ||
| 53 | + trigger(values, form) { | ||
| 54 | + if (values.selectAccount) { | ||
| 55 | + const findUser = MOCK_USER_OPTIONS.find( | ||
| 56 | + (item) => item.value === values.selectAccount, | ||
| 57 | + ); | ||
| 58 | + if (findUser) { | ||
| 59 | + form.setValues({ | ||
| 60 | + password: '123456', | ||
| 61 | + username: findUser.value, | ||
| 62 | + }); | ||
| 63 | + } | ||
| 64 | + } | ||
| 65 | + }, | ||
| 66 | + triggerFields: ['selectAccount'], | ||
| 67 | + }, | ||
| 68 | + fieldName: 'username', | ||
| 69 | + label: $t('authentication.username'), | ||
| 70 | + rules: z.string().min(1, { message: $t('authentication.usernameTip') }), | ||
| 71 | + }, | ||
| 72 | + { | ||
| 73 | + component: 'VbenInputPassword', | ||
| 74 | + componentProps: { | ||
| 75 | + placeholder: $t('authentication.password'), | ||
| 76 | + }, | ||
| 77 | + fieldName: 'password', | ||
| 78 | + label: $t('authentication.password'), | ||
| 79 | + rules: z.string().min(1, { message: $t('authentication.passwordTip') }), | ||
| 80 | + }, | ||
| 81 | + { | ||
| 82 | + component: markRaw(SliderCaptcha), | ||
| 83 | + fieldName: 'captcha', | ||
| 84 | + rules: z.boolean().refine((value) => value, { | ||
| 85 | + message: $t('authentication.verifyRequiredTip'), | ||
| 86 | + }), | ||
| 87 | + }, | ||
| 88 | + ]; | ||
| 89 | +}); | ||
| 90 | +</script> | ||
| 91 | + | ||
| 92 | +<template> | ||
| 93 | + <AuthenticationLogin | ||
| 94 | + :form-schema="formSchema" | ||
| 95 | + :loading="authStore.loginLoading" | ||
| 96 | + @submit="authStore.authLogin" | ||
| 97 | + /> | ||
| 98 | +</template> |
apps/web-payment/src/views/_core/authentication/qrcode-login.vue
0 → 100644
| 1 | +++ a/apps/web-payment/src/views/_core/authentication/qrcode-login.vue | ||
| 1 | +<script lang="ts" setup> | ||
| 2 | +import { AuthenticationQrCodeLogin } from '@vben/common-ui'; | ||
| 3 | +import { LOGIN_PATH } from '@vben/constants'; | ||
| 4 | + | ||
| 5 | +defineOptions({ name: 'QrCodeLogin' }); | ||
| 6 | +</script> | ||
| 7 | + | ||
| 8 | +<template> | ||
| 9 | + <AuthenticationQrCodeLogin :login-path="LOGIN_PATH" /> | ||
| 10 | +</template> |
apps/web-payment/src/views/_core/authentication/register.vue
0 → 100644
| 1 | +++ a/apps/web-payment/src/views/_core/authentication/register.vue | ||
| 1 | +<script lang="ts" setup> | ||
| 2 | +import type { VbenFormSchema } from '@vben/common-ui'; | ||
| 3 | +import type { Recordable } from '@vben/types'; | ||
| 4 | + | ||
| 5 | +import { computed, h, ref } from 'vue'; | ||
| 6 | + | ||
| 7 | +import { AuthenticationRegister, z } from '@vben/common-ui'; | ||
| 8 | +import { $t } from '@vben/locales'; | ||
| 9 | + | ||
| 10 | +defineOptions({ name: 'Register' }); | ||
| 11 | + | ||
| 12 | +const loading = ref(false); | ||
| 13 | + | ||
| 14 | +const formSchema = computed((): VbenFormSchema[] => { | ||
| 15 | + return [ | ||
| 16 | + { | ||
| 17 | + component: 'VbenInput', | ||
| 18 | + componentProps: { | ||
| 19 | + placeholder: $t('authentication.usernameTip'), | ||
| 20 | + }, | ||
| 21 | + fieldName: 'username', | ||
| 22 | + label: $t('authentication.username'), | ||
| 23 | + rules: z.string().min(1, { message: $t('authentication.usernameTip') }), | ||
| 24 | + }, | ||
| 25 | + { | ||
| 26 | + component: 'VbenInputPassword', | ||
| 27 | + componentProps: { | ||
| 28 | + passwordStrength: true, | ||
| 29 | + placeholder: $t('authentication.password'), | ||
| 30 | + }, | ||
| 31 | + fieldName: 'password', | ||
| 32 | + label: $t('authentication.password'), | ||
| 33 | + renderComponentContent() { | ||
| 34 | + return { | ||
| 35 | + strengthText: () => $t('authentication.passwordStrength'), | ||
| 36 | + }; | ||
| 37 | + }, | ||
| 38 | + rules: z.string().min(1, { message: $t('authentication.passwordTip') }), | ||
| 39 | + }, | ||
| 40 | + { | ||
| 41 | + component: 'VbenInputPassword', | ||
| 42 | + componentProps: { | ||
| 43 | + placeholder: $t('authentication.confirmPassword'), | ||
| 44 | + }, | ||
| 45 | + dependencies: { | ||
| 46 | + rules(values) { | ||
| 47 | + const { password } = values; | ||
| 48 | + return z | ||
| 49 | + .string({ required_error: $t('authentication.passwordTip') }) | ||
| 50 | + .min(1, { message: $t('authentication.passwordTip') }) | ||
| 51 | + .refine((value) => value === password, { | ||
| 52 | + message: $t('authentication.confirmPasswordTip'), | ||
| 53 | + }); | ||
| 54 | + }, | ||
| 55 | + triggerFields: ['password'], | ||
| 56 | + }, | ||
| 57 | + fieldName: 'confirmPassword', | ||
| 58 | + label: $t('authentication.confirmPassword'), | ||
| 59 | + }, | ||
| 60 | + { | ||
| 61 | + component: 'VbenCheckbox', | ||
| 62 | + fieldName: 'agreePolicy', | ||
| 63 | + renderComponentContent: () => ({ | ||
| 64 | + default: () => | ||
| 65 | + h('span', [ | ||
| 66 | + $t('authentication.agree'), | ||
| 67 | + h( | ||
| 68 | + 'a', | ||
| 69 | + { | ||
| 70 | + class: 'vben-link ml-1 ', | ||
| 71 | + href: '', | ||
| 72 | + }, | ||
| 73 | + `${$t('authentication.privacyPolicy')} & ${$t('authentication.terms')}`, | ||
| 74 | + ), | ||
| 75 | + ]), | ||
| 76 | + }), | ||
| 77 | + rules: z.boolean().refine((value) => !!value, { | ||
| 78 | + message: $t('authentication.agreeTip'), | ||
| 79 | + }), | ||
| 80 | + }, | ||
| 81 | + ]; | ||
| 82 | +}); | ||
| 83 | + | ||
| 84 | +function handleSubmit(value: Recordable<any>) { | ||
| 85 | + // eslint-disable-next-line no-console | ||
| 86 | + console.log('register submit:', value); | ||
| 87 | +} | ||
| 88 | +</script> | ||
| 89 | + | ||
| 90 | +<template> | ||
| 91 | + <AuthenticationRegister | ||
| 92 | + :form-schema="formSchema" | ||
| 93 | + :loading="loading" | ||
| 94 | + @submit="handleSubmit" | ||
| 95 | + /> | ||
| 96 | +</template> |
apps/web-payment/src/views/_core/fallback/coming-soon.vue
0 → 100644
apps/web-payment/src/views/_core/fallback/forbidden.vue
0 → 100644
apps/web-payment/src/views/_core/fallback/internal-error.vue
0 → 100644
apps/web-payment/src/views/_core/fallback/not-found.vue
0 → 100644
apps/web-payment/src/views/_core/fallback/offline.vue
0 → 100644
apps/web-payment/src/views/_core/profile/base-setting.vue
0 → 100644
| 1 | +++ a/apps/web-payment/src/views/_core/profile/base-setting.vue | ||
| 1 | +<script setup lang="ts"> | ||
| 2 | +import type { BasicOption } from '@vben/types'; | ||
| 3 | + | ||
| 4 | +import type { VbenFormSchema } from '#/adapter/form'; | ||
| 5 | + | ||
| 6 | +import { computed, onMounted, ref } from 'vue'; | ||
| 7 | + | ||
| 8 | +import { ProfileBaseSetting } from '@vben/common-ui'; | ||
| 9 | + | ||
| 10 | +import { getUserInfoApi } from '#/api'; | ||
| 11 | + | ||
| 12 | +const profileBaseSettingRef = ref(); | ||
| 13 | + | ||
| 14 | +const MOCK_ROLES_OPTIONS: BasicOption[] = [ | ||
| 15 | + { | ||
| 16 | + label: '管理员', | ||
| 17 | + value: 'super', | ||
| 18 | + }, | ||
| 19 | + { | ||
| 20 | + label: '用户', | ||
| 21 | + value: 'user', | ||
| 22 | + }, | ||
| 23 | + { | ||
| 24 | + label: '测试', | ||
| 25 | + value: 'test', | ||
| 26 | + }, | ||
| 27 | +]; | ||
| 28 | + | ||
| 29 | +const formSchema = computed((): VbenFormSchema[] => { | ||
| 30 | + return [ | ||
| 31 | + { | ||
| 32 | + fieldName: 'realName', | ||
| 33 | + component: 'Input', | ||
| 34 | + label: '姓名', | ||
| 35 | + }, | ||
| 36 | + { | ||
| 37 | + fieldName: 'username', | ||
| 38 | + component: 'Input', | ||
| 39 | + label: '用户名', | ||
| 40 | + }, | ||
| 41 | + { | ||
| 42 | + fieldName: 'roles', | ||
| 43 | + component: 'Select', | ||
| 44 | + componentProps: { | ||
| 45 | + mode: 'tags', | ||
| 46 | + options: MOCK_ROLES_OPTIONS, | ||
| 47 | + }, | ||
| 48 | + label: '角色', | ||
| 49 | + }, | ||
| 50 | + { | ||
| 51 | + fieldName: 'introduction', | ||
| 52 | + component: 'Textarea', | ||
| 53 | + label: '个人简介', | ||
| 54 | + }, | ||
| 55 | + ]; | ||
| 56 | +}); | ||
| 57 | + | ||
| 58 | +onMounted(async () => { | ||
| 59 | + const data = await getUserInfoApi(); | ||
| 60 | + profileBaseSettingRef.value.getFormApi().setValues(data); | ||
| 61 | +}); | ||
| 62 | +</script> | ||
| 63 | +<template> | ||
| 64 | + <ProfileBaseSetting ref="profileBaseSettingRef" :form-schema="formSchema" /> | ||
| 65 | +</template> |
apps/web-payment/src/views/_core/profile/index.vue
0 → 100644
| 1 | +++ a/apps/web-payment/src/views/_core/profile/index.vue | ||
| 1 | +<script setup lang="ts"> | ||
| 2 | +import { ref } from 'vue'; | ||
| 3 | + | ||
| 4 | +import { Profile } from '@vben/common-ui'; | ||
| 5 | +import { useUserStore } from '@vben/stores'; | ||
| 6 | + | ||
| 7 | +import ProfileBase from './base-setting.vue'; | ||
| 8 | +import ProfileNotificationSetting from './notification-setting.vue'; | ||
| 9 | +import ProfilePasswordSetting from './password-setting.vue'; | ||
| 10 | +import ProfileSecuritySetting from './security-setting.vue'; | ||
| 11 | + | ||
| 12 | +const userStore = useUserStore(); | ||
| 13 | + | ||
| 14 | +const tabsValue = ref<string>('basic'); | ||
| 15 | + | ||
| 16 | +const tabs = ref([ | ||
| 17 | + { | ||
| 18 | + label: '基本设置', | ||
| 19 | + value: 'basic', | ||
| 20 | + }, | ||
| 21 | + { | ||
| 22 | + label: '安全设置', | ||
| 23 | + value: 'security', | ||
| 24 | + }, | ||
| 25 | + { | ||
| 26 | + label: '修改密码', | ||
| 27 | + value: 'password', | ||
| 28 | + }, | ||
| 29 | + { | ||
| 30 | + label: '新消息提醒', | ||
| 31 | + value: 'notice', | ||
| 32 | + }, | ||
| 33 | +]); | ||
| 34 | +</script> | ||
| 35 | +<template> | ||
| 36 | + <Profile | ||
| 37 | + v-model:model-value="tabsValue" | ||
| 38 | + title="个人中心" | ||
| 39 | + :user-info="userStore.userInfo" | ||
| 40 | + :tabs="tabs" | ||
| 41 | + > | ||
| 42 | + <template #content> | ||
| 43 | + <ProfileBase v-if="tabsValue === 'basic'" /> | ||
| 44 | + <ProfileSecuritySetting v-if="tabsValue === 'security'" /> | ||
| 45 | + <ProfilePasswordSetting v-if="tabsValue === 'password'" /> | ||
| 46 | + <ProfileNotificationSetting v-if="tabsValue === 'notice'" /> | ||
| 47 | + </template> | ||
| 48 | + </Profile> | ||
| 49 | +</template> |
apps/web-payment/src/views/_core/profile/notification-setting.vue
0 → 100644
| 1 | +++ a/apps/web-payment/src/views/_core/profile/notification-setting.vue | ||
| 1 | +<script setup lang="ts"> | ||
| 2 | +import { computed } from 'vue'; | ||
| 3 | + | ||
| 4 | +import { ProfileNotificationSetting } from '@vben/common-ui'; | ||
| 5 | + | ||
| 6 | +const formSchema = computed(() => { | ||
| 7 | + return [ | ||
| 8 | + { | ||
| 9 | + value: true, | ||
| 10 | + fieldName: 'accountPassword', | ||
| 11 | + label: '账户密码', | ||
| 12 | + description: '其他用户的消息将以站内信的形式通知', | ||
| 13 | + }, | ||
| 14 | + { | ||
| 15 | + value: true, | ||
| 16 | + fieldName: 'systemMessage', | ||
| 17 | + label: '系统消息', | ||
| 18 | + description: '系统消息将以站内信的形式通知', | ||
| 19 | + }, | ||
| 20 | + { | ||
| 21 | + value: true, | ||
| 22 | + fieldName: 'todoTask', | ||
| 23 | + label: '待办任务', | ||
| 24 | + description: '待办任务将以站内信的形式通知', | ||
| 25 | + }, | ||
| 26 | + ]; | ||
| 27 | +}); | ||
| 28 | +</script> | ||
| 29 | +<template> | ||
| 30 | + <ProfileNotificationSetting :form-schema="formSchema" /> | ||
| 31 | +</template> |
apps/web-payment/src/views/_core/profile/password-setting.vue
0 → 100644
| 1 | +++ a/apps/web-payment/src/views/_core/profile/password-setting.vue | ||
| 1 | +<script setup lang="ts"> | ||
| 2 | +import type { VbenFormSchema } from '#/adapter/form'; | ||
| 3 | + | ||
| 4 | +import { computed, ref } from 'vue'; | ||
| 5 | + | ||
| 6 | +import { ProfilePasswordSetting, z } from '@vben/common-ui'; | ||
| 7 | + | ||
| 8 | +import { ElMessage } from 'element-plus'; | ||
| 9 | + | ||
| 10 | +const profilePasswordSettingRef = ref(); | ||
| 11 | + | ||
| 12 | +const formSchema = computed((): VbenFormSchema[] => { | ||
| 13 | + return [ | ||
| 14 | + { | ||
| 15 | + fieldName: 'oldPassword', | ||
| 16 | + label: '旧密码', | ||
| 17 | + component: 'VbenInputPassword', | ||
| 18 | + componentProps: { | ||
| 19 | + placeholder: '请输入旧密码', | ||
| 20 | + }, | ||
| 21 | + }, | ||
| 22 | + { | ||
| 23 | + fieldName: 'newPassword', | ||
| 24 | + label: '新密码', | ||
| 25 | + component: 'VbenInputPassword', | ||
| 26 | + componentProps: { | ||
| 27 | + passwordStrength: true, | ||
| 28 | + placeholder: '请输入新密码', | ||
| 29 | + }, | ||
| 30 | + }, | ||
| 31 | + { | ||
| 32 | + fieldName: 'confirmPassword', | ||
| 33 | + label: '确认密码', | ||
| 34 | + component: 'VbenInputPassword', | ||
| 35 | + componentProps: { | ||
| 36 | + passwordStrength: true, | ||
| 37 | + placeholder: '请再次输入新密码', | ||
| 38 | + }, | ||
| 39 | + dependencies: { | ||
| 40 | + rules(values) { | ||
| 41 | + const { newPassword } = values; | ||
| 42 | + return z | ||
| 43 | + .string({ required_error: '请再次输入新密码' }) | ||
| 44 | + .min(1, { message: '请再次输入新密码' }) | ||
| 45 | + .refine((value) => value === newPassword, { | ||
| 46 | + message: '两次输入的密码不一致', | ||
| 47 | + }); | ||
| 48 | + }, | ||
| 49 | + triggerFields: ['newPassword'], | ||
| 50 | + }, | ||
| 51 | + }, | ||
| 52 | + ]; | ||
| 53 | +}); | ||
| 54 | + | ||
| 55 | +function handleSubmit() { | ||
| 56 | + ElMessage.success('密码修改成功'); | ||
| 57 | +} | ||
| 58 | +</script> | ||
| 59 | +<template> | ||
| 60 | + <ProfilePasswordSetting | ||
| 61 | + ref="profilePasswordSettingRef" | ||
| 62 | + class="w-1/3" | ||
| 63 | + :form-schema="formSchema" | ||
| 64 | + @submit="handleSubmit" | ||
| 65 | + /> | ||
| 66 | +</template> |
apps/web-payment/src/views/_core/profile/security-setting.vue
0 → 100644
| 1 | +++ a/apps/web-payment/src/views/_core/profile/security-setting.vue | ||
| 1 | +<script setup lang="ts"> | ||
| 2 | +import { computed } from 'vue'; | ||
| 3 | + | ||
| 4 | +import { ProfileSecuritySetting } from '@vben/common-ui'; | ||
| 5 | + | ||
| 6 | +const formSchema = computed(() => { | ||
| 7 | + return [ | ||
| 8 | + { | ||
| 9 | + value: true, | ||
| 10 | + fieldName: 'accountPassword', | ||
| 11 | + label: '账户密码', | ||
| 12 | + description: '当前密码强度:强', | ||
| 13 | + }, | ||
| 14 | + { | ||
| 15 | + value: true, | ||
| 16 | + fieldName: 'securityPhone', | ||
| 17 | + label: '密保手机', | ||
| 18 | + description: '已绑定手机:138****8293', | ||
| 19 | + }, | ||
| 20 | + { | ||
| 21 | + value: true, | ||
| 22 | + fieldName: 'securityQuestion', | ||
| 23 | + label: '密保问题', | ||
| 24 | + description: '未设置密保问题,密保问题可有效保护账户安全', | ||
| 25 | + }, | ||
| 26 | + { | ||
| 27 | + value: true, | ||
| 28 | + fieldName: 'securityEmail', | ||
| 29 | + label: '备用邮箱', | ||
| 30 | + description: '已绑定邮箱:ant***sign.com', | ||
| 31 | + }, | ||
| 32 | + { | ||
| 33 | + value: false, | ||
| 34 | + fieldName: 'securityMfa', | ||
| 35 | + label: 'MFA 设备', | ||
| 36 | + description: '未绑定 MFA 设备,绑定后,可以进行二次确认', | ||
| 37 | + }, | ||
| 38 | + ]; | ||
| 39 | +}); | ||
| 40 | +</script> | ||
| 41 | +<template> | ||
| 42 | + <ProfileSecuritySetting :form-schema="formSchema" /> | ||
| 43 | +</template> |
apps/web-payment/src/views/dashboard/analytics/analytics-trends.vue
0 → 100644
| 1 | +++ a/apps/web-payment/src/views/dashboard/analytics/analytics-trends.vue | ||
| 1 | +<script lang="ts" setup> | ||
| 2 | +import type { EchartsUIType } from '@vben/plugins/echarts'; | ||
| 3 | + | ||
| 4 | +import { onMounted, ref } from 'vue'; | ||
| 5 | + | ||
| 6 | +import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; | ||
| 7 | + | ||
| 8 | +const chartRef = ref<EchartsUIType>(); | ||
| 9 | +const { renderEcharts } = useEcharts(chartRef); | ||
| 10 | + | ||
| 11 | +onMounted(() => { | ||
| 12 | + renderEcharts({ | ||
| 13 | + grid: { | ||
| 14 | + bottom: 0, | ||
| 15 | + containLabel: true, | ||
| 16 | + left: '1%', | ||
| 17 | + right: '1%', | ||
| 18 | + top: '2 %', | ||
| 19 | + }, | ||
| 20 | + series: [ | ||
| 21 | + { | ||
| 22 | + areaStyle: {}, | ||
| 23 | + data: [ | ||
| 24 | + 111, 2000, 6000, 16_000, 33_333, 55_555, 64_000, 33_333, 18_000, | ||
| 25 | + 36_000, 70_000, 42_444, 23_222, 13_000, 8000, 4000, 1200, 333, 222, | ||
| 26 | + 111, | ||
| 27 | + ], | ||
| 28 | + itemStyle: { | ||
| 29 | + color: '#5ab1ef', | ||
| 30 | + }, | ||
| 31 | + smooth: true, | ||
| 32 | + type: 'line', | ||
| 33 | + }, | ||
| 34 | + { | ||
| 35 | + areaStyle: {}, | ||
| 36 | + data: [ | ||
| 37 | + 33, 66, 88, 333, 3333, 6200, 20_000, 3000, 1200, 13_000, 22_000, | ||
| 38 | + 11_000, 2221, 1201, 390, 198, 60, 30, 22, 11, | ||
| 39 | + ], | ||
| 40 | + itemStyle: { | ||
| 41 | + color: '#019680', | ||
| 42 | + }, | ||
| 43 | + smooth: true, | ||
| 44 | + type: 'line', | ||
| 45 | + }, | ||
| 46 | + ], | ||
| 47 | + tooltip: { | ||
| 48 | + axisPointer: { | ||
| 49 | + lineStyle: { | ||
| 50 | + color: '#019680', | ||
| 51 | + width: 1, | ||
| 52 | + }, | ||
| 53 | + }, | ||
| 54 | + trigger: 'axis', | ||
| 55 | + }, | ||
| 56 | + // xAxis: { | ||
| 57 | + // axisTick: { | ||
| 58 | + // show: false, | ||
| 59 | + // }, | ||
| 60 | + // boundaryGap: false, | ||
| 61 | + // data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`), | ||
| 62 | + // type: 'category', | ||
| 63 | + // }, | ||
| 64 | + xAxis: { | ||
| 65 | + axisTick: { | ||
| 66 | + show: false, | ||
| 67 | + }, | ||
| 68 | + boundaryGap: false, | ||
| 69 | + data: Array.from({ length: 18 }).map((_item, index) => `${index + 6}:00`), | ||
| 70 | + splitLine: { | ||
| 71 | + lineStyle: { | ||
| 72 | + type: 'solid', | ||
| 73 | + width: 1, | ||
| 74 | + }, | ||
| 75 | + show: true, | ||
| 76 | + }, | ||
| 77 | + type: 'category', | ||
| 78 | + }, | ||
| 79 | + yAxis: [ | ||
| 80 | + { | ||
| 81 | + axisTick: { | ||
| 82 | + show: false, | ||
| 83 | + }, | ||
| 84 | + max: 80_000, | ||
| 85 | + splitArea: { | ||
| 86 | + show: true, | ||
| 87 | + }, | ||
| 88 | + splitNumber: 4, | ||
| 89 | + type: 'value', | ||
| 90 | + }, | ||
| 91 | + ], | ||
| 92 | + }); | ||
| 93 | +}); | ||
| 94 | +</script> | ||
| 95 | + | ||
| 96 | +<template> | ||
| 97 | + <EchartsUI ref="chartRef" /> | ||
| 98 | +</template> |
apps/web-payment/src/views/dashboard/analytics/analytics-visits-data.vue
0 → 100644
| 1 | +++ a/apps/web-payment/src/views/dashboard/analytics/analytics-visits-data.vue | ||
| 1 | +<script lang="ts" setup> | ||
| 2 | +import type { EchartsUIType } from '@vben/plugins/echarts'; | ||
| 3 | + | ||
| 4 | +import { onMounted, ref } from 'vue'; | ||
| 5 | + | ||
| 6 | +import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; | ||
| 7 | + | ||
| 8 | +const chartRef = ref<EchartsUIType>(); | ||
| 9 | +const { renderEcharts } = useEcharts(chartRef); | ||
| 10 | + | ||
| 11 | +onMounted(() => { | ||
| 12 | + renderEcharts({ | ||
| 13 | + legend: { | ||
| 14 | + bottom: 0, | ||
| 15 | + data: ['访问', '趋势'], | ||
| 16 | + }, | ||
| 17 | + radar: { | ||
| 18 | + indicator: [ | ||
| 19 | + { | ||
| 20 | + name: '网页', | ||
| 21 | + }, | ||
| 22 | + { | ||
| 23 | + name: '移动端', | ||
| 24 | + }, | ||
| 25 | + { | ||
| 26 | + name: 'Ipad', | ||
| 27 | + }, | ||
| 28 | + { | ||
| 29 | + name: '客户端', | ||
| 30 | + }, | ||
| 31 | + { | ||
| 32 | + name: '第三方', | ||
| 33 | + }, | ||
| 34 | + { | ||
| 35 | + name: '其它', | ||
| 36 | + }, | ||
| 37 | + ], | ||
| 38 | + radius: '60%', | ||
| 39 | + splitNumber: 8, | ||
| 40 | + }, | ||
| 41 | + series: [ | ||
| 42 | + { | ||
| 43 | + areaStyle: { | ||
| 44 | + opacity: 1, | ||
| 45 | + shadowBlur: 0, | ||
| 46 | + shadowColor: 'rgba(0,0,0,.2)', | ||
| 47 | + shadowOffsetX: 0, | ||
| 48 | + shadowOffsetY: 10, | ||
| 49 | + }, | ||
| 50 | + data: [ | ||
| 51 | + { | ||
| 52 | + itemStyle: { | ||
| 53 | + color: '#b6a2de', | ||
| 54 | + }, | ||
| 55 | + name: '访问', | ||
| 56 | + value: [90, 50, 86, 40, 50, 20], | ||
| 57 | + }, | ||
| 58 | + { | ||
| 59 | + itemStyle: { | ||
| 60 | + color: '#5ab1ef', | ||
| 61 | + }, | ||
| 62 | + name: '趋势', | ||
| 63 | + value: [70, 75, 70, 76, 20, 85], | ||
| 64 | + }, | ||
| 65 | + ], | ||
| 66 | + itemStyle: { | ||
| 67 | + // borderColor: '#fff', | ||
| 68 | + borderRadius: 10, | ||
| 69 | + borderWidth: 2, | ||
| 70 | + }, | ||
| 71 | + symbolSize: 0, | ||
| 72 | + type: 'radar', | ||
| 73 | + }, | ||
| 74 | + ], | ||
| 75 | + tooltip: {}, | ||
| 76 | + }); | ||
| 77 | +}); | ||
| 78 | +</script> | ||
| 79 | + | ||
| 80 | +<template> | ||
| 81 | + <EchartsUI ref="chartRef" /> | ||
| 82 | +</template> |
apps/web-payment/src/views/dashboard/analytics/analytics-visits-sales.vue
0 → 100644
| 1 | +++ a/apps/web-payment/src/views/dashboard/analytics/analytics-visits-sales.vue | ||
| 1 | +<script lang="ts" setup> | ||
| 2 | +import type { EchartsUIType } from '@vben/plugins/echarts'; | ||
| 3 | + | ||
| 4 | +import { onMounted, ref } from 'vue'; | ||
| 5 | + | ||
| 6 | +import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; | ||
| 7 | + | ||
| 8 | +const chartRef = ref<EchartsUIType>(); | ||
| 9 | +const { renderEcharts } = useEcharts(chartRef); | ||
| 10 | + | ||
| 11 | +onMounted(() => { | ||
| 12 | + renderEcharts({ | ||
| 13 | + series: [ | ||
| 14 | + { | ||
| 15 | + animationDelay() { | ||
| 16 | + return Math.random() * 400; | ||
| 17 | + }, | ||
| 18 | + animationEasing: 'exponentialInOut', | ||
| 19 | + animationType: 'scale', | ||
| 20 | + center: ['50%', '50%'], | ||
| 21 | + color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'], | ||
| 22 | + data: [ | ||
| 23 | + { name: '外包', value: 500 }, | ||
| 24 | + { name: '定制', value: 310 }, | ||
| 25 | + { name: '技术支持', value: 274 }, | ||
| 26 | + { name: '远程', value: 400 }, | ||
| 27 | + ].toSorted((a, b) => { | ||
| 28 | + return a.value - b.value; | ||
| 29 | + }), | ||
| 30 | + name: '商业占比', | ||
| 31 | + radius: '80%', | ||
| 32 | + roseType: 'radius', | ||
| 33 | + type: 'pie', | ||
| 34 | + }, | ||
| 35 | + ], | ||
| 36 | + | ||
| 37 | + tooltip: { | ||
| 38 | + trigger: 'item', | ||
| 39 | + }, | ||
| 40 | + }); | ||
| 41 | +}); | ||
| 42 | +</script> | ||
| 43 | + | ||
| 44 | +<template> | ||
| 45 | + <EchartsUI ref="chartRef" /> | ||
| 46 | +</template> |
apps/web-payment/src/views/dashboard/analytics/analytics-visits-source.vue
0 → 100644
| 1 | +++ a/apps/web-payment/src/views/dashboard/analytics/analytics-visits-source.vue | ||
| 1 | +<script lang="ts" setup> | ||
| 2 | +import type { EchartsUIType } from '@vben/plugins/echarts'; | ||
| 3 | + | ||
| 4 | +import { onMounted, ref } from 'vue'; | ||
| 5 | + | ||
| 6 | +import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; | ||
| 7 | + | ||
| 8 | +const chartRef = ref<EchartsUIType>(); | ||
| 9 | +const { renderEcharts } = useEcharts(chartRef); | ||
| 10 | + | ||
| 11 | +onMounted(() => { | ||
| 12 | + renderEcharts({ | ||
| 13 | + legend: { | ||
| 14 | + bottom: '2%', | ||
| 15 | + left: 'center', | ||
| 16 | + }, | ||
| 17 | + series: [ | ||
| 18 | + { | ||
| 19 | + animationDelay() { | ||
| 20 | + return Math.random() * 100; | ||
| 21 | + }, | ||
| 22 | + animationEasing: 'exponentialInOut', | ||
| 23 | + animationType: 'scale', | ||
| 24 | + avoidLabelOverlap: false, | ||
| 25 | + color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'], | ||
| 26 | + data: [ | ||
| 27 | + { name: '搜索引擎', value: 1048 }, | ||
| 28 | + { name: '直接访问', value: 735 }, | ||
| 29 | + { name: '邮件营销', value: 580 }, | ||
| 30 | + { name: '联盟广告', value: 484 }, | ||
| 31 | + ], | ||
| 32 | + emphasis: { | ||
| 33 | + label: { | ||
| 34 | + fontSize: '12', | ||
| 35 | + fontWeight: 'bold', | ||
| 36 | + show: true, | ||
| 37 | + }, | ||
| 38 | + }, | ||
| 39 | + itemStyle: { | ||
| 40 | + // borderColor: '#fff', | ||
| 41 | + borderRadius: 10, | ||
| 42 | + borderWidth: 2, | ||
| 43 | + }, | ||
| 44 | + label: { | ||
| 45 | + position: 'center', | ||
| 46 | + show: false, | ||
| 47 | + }, | ||
| 48 | + labelLine: { | ||
| 49 | + show: false, | ||
| 50 | + }, | ||
| 51 | + name: '访问来源', | ||
| 52 | + radius: ['40%', '65%'], | ||
| 53 | + type: 'pie', | ||
| 54 | + }, | ||
| 55 | + ], | ||
| 56 | + tooltip: { | ||
| 57 | + trigger: 'item', | ||
| 58 | + }, | ||
| 59 | + }); | ||
| 60 | +}); | ||
| 61 | +</script> | ||
| 62 | + | ||
| 63 | +<template> | ||
| 64 | + <EchartsUI ref="chartRef" /> | ||
| 65 | +</template> |
apps/web-payment/src/views/dashboard/analytics/analytics-visits.vue
0 → 100644
| 1 | +++ a/apps/web-payment/src/views/dashboard/analytics/analytics-visits.vue | ||
| 1 | +<script lang="ts" setup> | ||
| 2 | +import type { EchartsUIType } from '@vben/plugins/echarts'; | ||
| 3 | + | ||
| 4 | +import { onMounted, ref } from 'vue'; | ||
| 5 | + | ||
| 6 | +import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; | ||
| 7 | + | ||
| 8 | +const chartRef = ref<EchartsUIType>(); | ||
| 9 | +const { renderEcharts } = useEcharts(chartRef); | ||
| 10 | + | ||
| 11 | +onMounted(() => { | ||
| 12 | + renderEcharts({ | ||
| 13 | + grid: { | ||
| 14 | + bottom: 0, | ||
| 15 | + containLabel: true, | ||
| 16 | + left: '1%', | ||
| 17 | + right: '1%', | ||
| 18 | + top: '2 %', | ||
| 19 | + }, | ||
| 20 | + series: [ | ||
| 21 | + { | ||
| 22 | + barMaxWidth: 80, | ||
| 23 | + // color: '#4f69fd', | ||
| 24 | + data: [ | ||
| 25 | + 3000, 2000, 3333, 5000, 3200, 4200, 3200, 2100, 3000, 5100, 6000, | ||
| 26 | + 3200, 4800, | ||
| 27 | + ], | ||
| 28 | + type: 'bar', | ||
| 29 | + }, | ||
| 30 | + ], | ||
| 31 | + tooltip: { | ||
| 32 | + axisPointer: { | ||
| 33 | + lineStyle: { | ||
| 34 | + // color: '#4f69fd', | ||
| 35 | + width: 1, | ||
| 36 | + }, | ||
| 37 | + }, | ||
| 38 | + trigger: 'axis', | ||
| 39 | + }, | ||
| 40 | + xAxis: { | ||
| 41 | + data: Array.from({ length: 12 }).map((_item, index) => `${index + 1}月`), | ||
| 42 | + type: 'category', | ||
| 43 | + }, | ||
| 44 | + yAxis: { | ||
| 45 | + max: 8000, | ||
| 46 | + splitNumber: 4, | ||
| 47 | + type: 'value', | ||
| 48 | + }, | ||
| 49 | + }); | ||
| 50 | +}); | ||
| 51 | +</script> | ||
| 52 | + | ||
| 53 | +<template> | ||
| 54 | + <EchartsUI ref="chartRef" /> | ||
| 55 | +</template> |
apps/web-payment/src/views/dashboard/analytics/index.vue
0 → 100644
| 1 | +++ a/apps/web-payment/src/views/dashboard/analytics/index.vue | ||
| 1 | +<script lang="ts" setup> | ||
| 2 | +import type { AnalysisOverviewItem } from '@vben/common-ui'; | ||
| 3 | +import type { TabOption } from '@vben/types'; | ||
| 4 | + | ||
| 5 | +import { | ||
| 6 | + AnalysisChartCard, | ||
| 7 | + AnalysisChartsTabs, | ||
| 8 | + AnalysisOverview, | ||
| 9 | +} from '@vben/common-ui'; | ||
| 10 | +import { | ||
| 11 | + SvgBellIcon, | ||
| 12 | + SvgCakeIcon, | ||
| 13 | + SvgCardIcon, | ||
| 14 | + SvgDownloadIcon, | ||
| 15 | +} from '@vben/icons'; | ||
| 16 | + | ||
| 17 | +import AnalyticsTrends from './analytics-trends.vue'; | ||
| 18 | +import AnalyticsVisitsData from './analytics-visits-data.vue'; | ||
| 19 | +import AnalyticsVisitsSales from './analytics-visits-sales.vue'; | ||
| 20 | +import AnalyticsVisitsSource from './analytics-visits-source.vue'; | ||
| 21 | +import AnalyticsVisits from './analytics-visits.vue'; | ||
| 22 | + | ||
| 23 | +const overviewItems: AnalysisOverviewItem[] = [ | ||
| 24 | + { | ||
| 25 | + icon: SvgCardIcon, | ||
| 26 | + title: '用户量', | ||
| 27 | + totalTitle: '总用户量', | ||
| 28 | + totalValue: 120_000, | ||
| 29 | + value: 2000, | ||
| 30 | + }, | ||
| 31 | + { | ||
| 32 | + icon: SvgCakeIcon, | ||
| 33 | + title: '访问量', | ||
| 34 | + totalTitle: '总访问量', | ||
| 35 | + totalValue: 500_000, | ||
| 36 | + value: 20_000, | ||
| 37 | + }, | ||
| 38 | + { | ||
| 39 | + icon: SvgDownloadIcon, | ||
| 40 | + title: '下载量', | ||
| 41 | + totalTitle: '总下载量', | ||
| 42 | + totalValue: 120_000, | ||
| 43 | + value: 8000, | ||
| 44 | + }, | ||
| 45 | + { | ||
| 46 | + icon: SvgBellIcon, | ||
| 47 | + title: '使用量', | ||
| 48 | + totalTitle: '总使用量', | ||
| 49 | + totalValue: 50_000, | ||
| 50 | + value: 5000, | ||
| 51 | + }, | ||
| 52 | +]; | ||
| 53 | + | ||
| 54 | +const chartTabs: TabOption[] = [ | ||
| 55 | + { | ||
| 56 | + label: '流量趋势', | ||
| 57 | + value: 'trends', | ||
| 58 | + }, | ||
| 59 | + { | ||
| 60 | + label: '月访问量', | ||
| 61 | + value: 'visits', | ||
| 62 | + }, | ||
| 63 | +]; | ||
| 64 | +</script> | ||
| 65 | + | ||
| 66 | +<template> | ||
| 67 | + <div class="p-5"> | ||
| 68 | + <AnalysisOverview :items="overviewItems" /> | ||
| 69 | + <AnalysisChartsTabs :tabs="chartTabs" class="mt-5"> | ||
| 70 | + <template #trends> | ||
| 71 | + <AnalyticsTrends /> | ||
| 72 | + </template> | ||
| 73 | + <template #visits> | ||
| 74 | + <AnalyticsVisits /> | ||
| 75 | + </template> | ||
| 76 | + </AnalysisChartsTabs> | ||
| 77 | + | ||
| 78 | + <div class="mt-5 w-full md:flex"> | ||
| 79 | + <AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问数量"> | ||
| 80 | + <AnalyticsVisitsData /> | ||
| 81 | + </AnalysisChartCard> | ||
| 82 | + <AnalysisChartCard class="mt-5 md:mr-4 md:mt-0 md:w-1/3" title="访问来源"> | ||
| 83 | + <AnalyticsVisitsSource /> | ||
| 84 | + </AnalysisChartCard> | ||
| 85 | + <AnalysisChartCard class="mt-5 md:mt-0 md:w-1/3" title="访问来源"> | ||
| 86 | + <AnalyticsVisitsSales /> | ||
| 87 | + </AnalysisChartCard> | ||
| 88 | + </div> | ||
| 89 | + </div> | ||
| 90 | +</template> |
apps/web-payment/src/views/dashboard/workspace/index.vue
0 → 100644
| 1 | +++ a/apps/web-payment/src/views/dashboard/workspace/index.vue | ||
| 1 | +<script lang="ts" setup> | ||
| 2 | +import type { | ||
| 3 | + WorkbenchProjectItem, | ||
| 4 | + WorkbenchQuickNavItem, | ||
| 5 | + WorkbenchTodoItem, | ||
| 6 | + WorkbenchTrendItem, | ||
| 7 | +} from '@vben/common-ui'; | ||
| 8 | + | ||
| 9 | +import { ref } from 'vue'; | ||
| 10 | +import { useRouter } from 'vue-router'; | ||
| 11 | + | ||
| 12 | +import { | ||
| 13 | + AnalysisChartCard, | ||
| 14 | + WorkbenchHeader, | ||
| 15 | + WorkbenchProject, | ||
| 16 | + WorkbenchQuickNav, | ||
| 17 | + WorkbenchTodo, | ||
| 18 | + WorkbenchTrends, | ||
| 19 | +} from '@vben/common-ui'; | ||
| 20 | +import { preferences } from '@vben/preferences'; | ||
| 21 | +import { useUserStore } from '@vben/stores'; | ||
| 22 | +import { openWindow } from '@vben/utils'; | ||
| 23 | + | ||
| 24 | +import AnalyticsVisitsSource from '../analytics/analytics-visits-source.vue'; | ||
| 25 | + | ||
| 26 | +const userStore = useUserStore(); | ||
| 27 | + | ||
| 28 | +// 这是一个示例数据,实际项目中需要根据实际情况进行调整 | ||
| 29 | +// url 也可以是内部路由,在 navTo 方法中识别处理,进行内部跳转 | ||
| 30 | +// 例如:url: /dashboard/workspace | ||
| 31 | +const projectItems: WorkbenchProjectItem[] = [ | ||
| 32 | + { | ||
| 33 | + color: '', | ||
| 34 | + content: '不要等待机会,而要创造机会。', | ||
| 35 | + date: '2021-04-01', | ||
| 36 | + group: '开源组', | ||
| 37 | + icon: 'carbon:logo-github', | ||
| 38 | + title: 'Github', | ||
| 39 | + url: 'https://github.com', | ||
| 40 | + }, | ||
| 41 | + { | ||
| 42 | + color: '#3fb27f', | ||
| 43 | + content: '现在的你决定将来的你。', | ||
| 44 | + date: '2021-04-01', | ||
| 45 | + group: '算法组', | ||
| 46 | + icon: 'ion:logo-vue', | ||
| 47 | + title: 'Vue', | ||
| 48 | + url: 'https://vuejs.org', | ||
| 49 | + }, | ||
| 50 | + { | ||
| 51 | + color: '#e18525', | ||
| 52 | + content: '没有什么才能比努力更重要。', | ||
| 53 | + date: '2021-04-01', | ||
| 54 | + group: '上班摸鱼', | ||
| 55 | + icon: 'ion:logo-html5', | ||
| 56 | + title: 'Html5', | ||
| 57 | + url: 'https://developer.mozilla.org/zh-CN/docs/Web/HTML', | ||
| 58 | + }, | ||
| 59 | + { | ||
| 60 | + color: '#bf0c2c', | ||
| 61 | + content: '热情和欲望可以突破一切难关。', | ||
| 62 | + date: '2021-04-01', | ||
| 63 | + group: 'UI', | ||
| 64 | + icon: 'ion:logo-angular', | ||
| 65 | + title: 'Angular', | ||
| 66 | + url: 'https://angular.io', | ||
| 67 | + }, | ||
| 68 | + { | ||
| 69 | + color: '#00d8ff', | ||
| 70 | + content: '健康的身体是实现目标的基石。', | ||
| 71 | + date: '2021-04-01', | ||
| 72 | + group: '技术牛', | ||
| 73 | + icon: 'bx:bxl-react', | ||
| 74 | + title: 'React', | ||
| 75 | + url: 'https://reactjs.org', | ||
| 76 | + }, | ||
| 77 | + { | ||
| 78 | + color: '#EBD94E', | ||
| 79 | + content: '路是走出来的,而不是空想出来的。', | ||
| 80 | + date: '2021-04-01', | ||
| 81 | + group: '架构组', | ||
| 82 | + icon: 'ion:logo-javascript', | ||
| 83 | + title: 'Js', | ||
| 84 | + url: 'https://developer.mozilla.org/zh-CN/docs/Web/JavaScript', | ||
| 85 | + }, | ||
| 86 | +]; | ||
| 87 | + | ||
| 88 | +// 同样,这里的 url 也可以使用以 http 开头的外部链接 | ||
| 89 | +const quickNavItems: WorkbenchQuickNavItem[] = [ | ||
| 90 | + { | ||
| 91 | + color: '#1fdaca', | ||
| 92 | + icon: 'ion:home-outline', | ||
| 93 | + title: '首页', | ||
| 94 | + url: '/', | ||
| 95 | + }, | ||
| 96 | + { | ||
| 97 | + color: '#bf0c2c', | ||
| 98 | + icon: 'ion:grid-outline', | ||
| 99 | + title: '仪表盘', | ||
| 100 | + url: '/dashboard', | ||
| 101 | + }, | ||
| 102 | + { | ||
| 103 | + color: '#e18525', | ||
| 104 | + icon: 'ion:layers-outline', | ||
| 105 | + title: '组件', | ||
| 106 | + url: '/demos/features/icons', | ||
| 107 | + }, | ||
| 108 | + { | ||
| 109 | + color: '#3fb27f', | ||
| 110 | + icon: 'ion:settings-outline', | ||
| 111 | + title: '系统管理', | ||
| 112 | + url: '/demos/features/login-expired', // 这里的 URL 是示例,实际项目中需要根据实际情况进行调整 | ||
| 113 | + }, | ||
| 114 | + { | ||
| 115 | + color: '#4daf1bc9', | ||
| 116 | + icon: 'ion:key-outline', | ||
| 117 | + title: '权限管理', | ||
| 118 | + url: '/demos/access/page-control', | ||
| 119 | + }, | ||
| 120 | + { | ||
| 121 | + color: '#00d8ff', | ||
| 122 | + icon: 'ion:bar-chart-outline', | ||
| 123 | + title: '图表', | ||
| 124 | + url: '/analytics', | ||
| 125 | + }, | ||
| 126 | +]; | ||
| 127 | + | ||
| 128 | +const todoItems = ref<WorkbenchTodoItem[]>([ | ||
| 129 | + { | ||
| 130 | + completed: false, | ||
| 131 | + content: `审查最近提交到Git仓库的前端代码,确保代码质量和规范。`, | ||
| 132 | + date: '2024-07-30 11:00:00', | ||
| 133 | + title: '审查前端代码提交', | ||
| 134 | + }, | ||
| 135 | + { | ||
| 136 | + completed: true, | ||
| 137 | + content: `检查并优化系统性能,降低CPU使用率。`, | ||
| 138 | + date: '2024-07-30 11:00:00', | ||
| 139 | + title: '系统性能优化', | ||
| 140 | + }, | ||
| 141 | + { | ||
| 142 | + completed: false, | ||
| 143 | + content: `进行系统安全检查,确保没有安全漏洞或未授权的访问。 `, | ||
| 144 | + date: '2024-07-30 11:00:00', | ||
| 145 | + title: '安全检查', | ||
| 146 | + }, | ||
| 147 | + { | ||
| 148 | + completed: false, | ||
| 149 | + content: `更新项目中的所有npm依赖包,确保使用最新版本。`, | ||
| 150 | + date: '2024-07-30 11:00:00', | ||
| 151 | + title: '更新项目依赖', | ||
| 152 | + }, | ||
| 153 | + { | ||
| 154 | + completed: false, | ||
| 155 | + content: `修复用户报告的页面UI显示问题,确保在不同浏览器中显示一致。 `, | ||
| 156 | + date: '2024-07-30 11:00:00', | ||
| 157 | + title: '修复UI显示问题', | ||
| 158 | + }, | ||
| 159 | +]); | ||
| 160 | +const trendItems: WorkbenchTrendItem[] = [ | ||
| 161 | + { | ||
| 162 | + avatar: 'svg:avatar-1', | ||
| 163 | + content: `在 <a>开源组</a> 创建了项目 <a>Vue</a>`, | ||
| 164 | + date: '刚刚', | ||
| 165 | + title: '威廉', | ||
| 166 | + }, | ||
| 167 | + { | ||
| 168 | + avatar: 'svg:avatar-2', | ||
| 169 | + content: `关注了 <a>威廉</a> `, | ||
| 170 | + date: '1个小时前', | ||
| 171 | + title: '艾文', | ||
| 172 | + }, | ||
| 173 | + { | ||
| 174 | + avatar: 'svg:avatar-3', | ||
| 175 | + content: `发布了 <a>个人动态</a> `, | ||
| 176 | + date: '1天前', | ||
| 177 | + title: '克里斯', | ||
| 178 | + }, | ||
| 179 | + { | ||
| 180 | + avatar: 'svg:avatar-4', | ||
| 181 | + content: `发表文章 <a>如何编写一个Vite插件</a> `, | ||
| 182 | + date: '2天前', | ||
| 183 | + title: 'Vben', | ||
| 184 | + }, | ||
| 185 | + { | ||
| 186 | + avatar: 'svg:avatar-1', | ||
| 187 | + content: `回复了 <a>杰克</a> 的问题 <a>如何进行项目优化?</a>`, | ||
| 188 | + date: '3天前', | ||
| 189 | + title: '皮特', | ||
| 190 | + }, | ||
| 191 | + { | ||
| 192 | + avatar: 'svg:avatar-2', | ||
| 193 | + content: `关闭了问题 <a>如何运行项目</a> `, | ||
| 194 | + date: '1周前', | ||
| 195 | + title: '杰克', | ||
| 196 | + }, | ||
| 197 | + { | ||
| 198 | + avatar: 'svg:avatar-3', | ||
| 199 | + content: `发布了 <a>个人动态</a> `, | ||
| 200 | + date: '1周前', | ||
| 201 | + title: '威廉', | ||
| 202 | + }, | ||
| 203 | + { | ||
| 204 | + avatar: 'svg:avatar-4', | ||
| 205 | + content: `推送了代码到 <a>Github</a>`, | ||
| 206 | + date: '2021-04-01 20:00', | ||
| 207 | + title: '威廉', | ||
| 208 | + }, | ||
| 209 | + { | ||
| 210 | + avatar: 'svg:avatar-4', | ||
| 211 | + content: `发表文章 <a>如何编写使用 Admin Vben</a> `, | ||
| 212 | + date: '2021-03-01 20:00', | ||
| 213 | + title: 'Vben', | ||
| 214 | + }, | ||
| 215 | +]; | ||
| 216 | + | ||
| 217 | +const router = useRouter(); | ||
| 218 | + | ||
| 219 | +// 这是一个示例方法,实际项目中需要根据实际情况进行调整 | ||
| 220 | +// This is a sample method, adjust according to the actual project requirements | ||
| 221 | +function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) { | ||
| 222 | + if (nav.url?.startsWith('http')) { | ||
| 223 | + openWindow(nav.url); | ||
| 224 | + return; | ||
| 225 | + } | ||
| 226 | + if (nav.url?.startsWith('/')) { | ||
| 227 | + router.push(nav.url).catch((error) => { | ||
| 228 | + console.error('Navigation failed:', error); | ||
| 229 | + }); | ||
| 230 | + } else { | ||
| 231 | + console.warn(`Unknown URL for navigation item: ${nav.title} -> ${nav.url}`); | ||
| 232 | + } | ||
| 233 | +} | ||
| 234 | +</script> | ||
| 235 | + | ||
| 236 | +<template> | ||
| 237 | + <div class="p-5"> | ||
| 238 | + <WorkbenchHeader | ||
| 239 | + :avatar="userStore.userInfo?.avatar || preferences.app.defaultAvatar" | ||
| 240 | + > | ||
| 241 | + <template #title> | ||
| 242 | + 早安, {{ userStore.userInfo?.realName }}, 开始您一天的工作吧! | ||
| 243 | + </template> | ||
| 244 | + <template #description> 今日晴,20℃ - 32℃! </template> | ||
| 245 | + </WorkbenchHeader> | ||
| 246 | + | ||
| 247 | + <div class="mt-5 flex flex-col lg:flex-row"> | ||
| 248 | + <div class="mr-4 w-full lg:w-3/5"> | ||
| 249 | + <WorkbenchProject :items="projectItems" title="项目" @click="navTo" /> | ||
| 250 | + <WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" /> | ||
| 251 | + </div> | ||
| 252 | + <div class="w-full lg:w-2/5"> | ||
| 253 | + <WorkbenchQuickNav | ||
| 254 | + :items="quickNavItems" | ||
| 255 | + class="mt-5 lg:mt-0" | ||
| 256 | + title="快捷导航" | ||
| 257 | + @click="navTo" | ||
| 258 | + /> | ||
| 259 | + <WorkbenchTodo :items="todoItems" class="mt-5" title="待办事项" /> | ||
| 260 | + <AnalysisChartCard class="mt-5" title="访问来源"> | ||
| 261 | + <AnalyticsVisitsSource /> | ||
| 262 | + </AnalysisChartCard> | ||
| 263 | + </div> | ||
| 264 | + </div> | ||
| 265 | + </div> | ||
| 266 | +</template> |
apps/web-payment/src/views/demos/element/index.vue
0 → 100644
| 1 | +++ a/apps/web-payment/src/views/demos/element/index.vue | ||
| 1 | +<script lang="ts" setup> | ||
| 2 | +import { ref } from 'vue'; | ||
| 3 | + | ||
| 4 | +import { Page } from '@vben/common-ui'; | ||
| 5 | + | ||
| 6 | +import { | ||
| 7 | + ElButton, | ||
| 8 | + ElCard, | ||
| 9 | + ElMessage, | ||
| 10 | + ElNotification, | ||
| 11 | + ElSegmented, | ||
| 12 | + ElSpace, | ||
| 13 | + ElTable, | ||
| 14 | +} from 'element-plus'; | ||
| 15 | + | ||
| 16 | +type NotificationType = 'error' | 'info' | 'success' | 'warning'; | ||
| 17 | + | ||
| 18 | +function info() { | ||
| 19 | + ElMessage.info('How many roads must a man walk down'); | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +function error() { | ||
| 23 | + ElMessage.error({ | ||
| 24 | + duration: 2500, | ||
| 25 | + message: 'Once upon a time you dressed so fine', | ||
| 26 | + }); | ||
| 27 | +} | ||
| 28 | + | ||
| 29 | +function warning() { | ||
| 30 | + ElMessage.warning('How many roads must a man walk down'); | ||
| 31 | +} | ||
| 32 | +function success() { | ||
| 33 | + ElMessage.success( | ||
| 34 | + 'Cause you walked hand in hand With another man in my place', | ||
| 35 | + ); | ||
| 36 | +} | ||
| 37 | + | ||
| 38 | +function notify(type: NotificationType) { | ||
| 39 | + ElNotification({ | ||
| 40 | + duration: 2500, | ||
| 41 | + message: '说点啥呢', | ||
| 42 | + type, | ||
| 43 | + }); | ||
| 44 | +} | ||
| 45 | +const tableData = [ | ||
| 46 | + { prop1: '1', prop2: 'A' }, | ||
| 47 | + { prop1: '2', prop2: 'B' }, | ||
| 48 | + { prop1: '3', prop2: 'C' }, | ||
| 49 | + { prop1: '4', prop2: 'D' }, | ||
| 50 | + { prop1: '5', prop2: 'E' }, | ||
| 51 | + { prop1: '6', prop2: 'F' }, | ||
| 52 | +]; | ||
| 53 | + | ||
| 54 | +const segmentedValue = ref('Mon'); | ||
| 55 | + | ||
| 56 | +const segmentedOptions = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']; | ||
| 57 | +</script> | ||
| 58 | + | ||
| 59 | +<template> | ||
| 60 | + <Page | ||
| 61 | + description="支持多语言,主题功能集成切换等" | ||
| 62 | + title="Element Plus组件使用演示" | ||
| 63 | + > | ||
| 64 | + <div class="flex flex-wrap gap-5"> | ||
| 65 | + <ElCard class="mb-5 w-auto"> | ||
| 66 | + <template #header> 按钮 </template> | ||
| 67 | + <ElSpace> | ||
| 68 | + <ElButton text>Text</ElButton> | ||
| 69 | + <ElButton>Default</ElButton> | ||
| 70 | + <ElButton type="primary"> Primary </ElButton> | ||
| 71 | + <ElButton type="info"> Info </ElButton> | ||
| 72 | + <ElButton type="success"> Success </ElButton> | ||
| 73 | + <ElButton type="warning"> Warning </ElButton> | ||
| 74 | + <ElButton type="danger"> Error </ElButton> | ||
| 75 | + </ElSpace> | ||
| 76 | + </ElCard> | ||
| 77 | + <ElCard class="mb-5 w-80"> | ||
| 78 | + <template #header> Message </template> | ||
| 79 | + <ElSpace> | ||
| 80 | + <ElButton type="info" @click="info"> 信息 </ElButton> | ||
| 81 | + <ElButton type="danger" @click="error"> 错误 </ElButton> | ||
| 82 | + <ElButton type="warning" @click="warning"> 警告 </ElButton> | ||
| 83 | + <ElButton type="success" @click="success"> 成功 </ElButton> | ||
| 84 | + </ElSpace> | ||
| 85 | + </ElCard> | ||
| 86 | + <ElCard class="mb-5 w-80"> | ||
| 87 | + <template #header> Notification </template> | ||
| 88 | + <ElSpace> | ||
| 89 | + <ElButton type="info" @click="notify('info')"> 信息 </ElButton> | ||
| 90 | + <ElButton type="danger" @click="notify('error')"> 错误 </ElButton> | ||
| 91 | + <ElButton type="warning" @click="notify('warning')"> 警告 </ElButton> | ||
| 92 | + <ElButton type="success" @click="notify('success')"> 成功 </ElButton> | ||
| 93 | + </ElSpace> | ||
| 94 | + </ElCard> | ||
| 95 | + <ElCard class="mb-5 w-auto"> | ||
| 96 | + <template #header> Segmented </template> | ||
| 97 | + <ElSegmented | ||
| 98 | + v-model="segmentedValue" | ||
| 99 | + :options="segmentedOptions" | ||
| 100 | + size="large" | ||
| 101 | + /> | ||
| 102 | + </ElCard> | ||
| 103 | + <ElCard class="mb-5 w-80"> | ||
| 104 | + <template #header> V-Loading </template> | ||
| 105 | + <div class="flex size-72 items-center justify-center" v-loading="true"> | ||
| 106 | + 一些演示的内容 | ||
| 107 | + </div> | ||
| 108 | + </ElCard> | ||
| 109 | + <ElCard class="mb-5 w-80"> | ||
| 110 | + <ElTable :data="tableData" stripe> | ||
| 111 | + <ElTable.TableColumn label="测试列1" prop="prop1" /> | ||
| 112 | + <ElTable.TableColumn label="测试列2" prop="prop2" /> | ||
| 113 | + </ElTable> | ||
| 114 | + </ElCard> | ||
| 115 | + </div> | ||
| 116 | + </Page> | ||
| 117 | +</template> |
apps/web-payment/src/views/demos/form/basic.vue
0 → 100644
| 1 | +++ a/apps/web-payment/src/views/demos/form/basic.vue | ||
| 1 | +<script lang="ts" setup> | ||
| 2 | +import { h } from 'vue'; | ||
| 3 | + | ||
| 4 | +import { Page, useVbenDrawer } from '@vben/common-ui'; | ||
| 5 | + | ||
| 6 | +import { ElButton, ElCard, ElCheckbox, ElMessage } from 'element-plus'; | ||
| 7 | + | ||
| 8 | +import { useVbenForm } from '#/adapter/form'; | ||
| 9 | +import { getAllMenusApi } from '#/api'; | ||
| 10 | + | ||
| 11 | +const [Form, formApi] = useVbenForm({ | ||
| 12 | + commonConfig: { | ||
| 13 | + // 所有表单项 | ||
| 14 | + componentProps: { | ||
| 15 | + class: 'w-full', | ||
| 16 | + }, | ||
| 17 | + }, | ||
| 18 | + layout: 'horizontal', | ||
| 19 | + // 大屏一行显示3个,中屏一行显示2个,小屏一行显示1个 | ||
| 20 | + // wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3', | ||
| 21 | + handleSubmit: (values) => { | ||
| 22 | + ElMessage.success(`表单数据:${JSON.stringify(values)}`); | ||
| 23 | + }, | ||
| 24 | + schema: [ | ||
| 25 | + { | ||
| 26 | + component: 'IconPicker', | ||
| 27 | + fieldName: 'icon', | ||
| 28 | + label: 'IconPicker', | ||
| 29 | + }, | ||
| 30 | + { | ||
| 31 | + // 组件需要在 #/adapter.ts内注册,并加上类型 | ||
| 32 | + component: 'ApiSelect', | ||
| 33 | + // 对应组件的参数 | ||
| 34 | + componentProps: { | ||
| 35 | + // 菜单接口转options格式 | ||
| 36 | + afterFetch: (data: { name: string; path: string }[]) => { | ||
| 37 | + return data.map((item: any) => ({ | ||
| 38 | + label: item.name, | ||
| 39 | + value: item.path, | ||
| 40 | + })); | ||
| 41 | + }, | ||
| 42 | + // 菜单接口 | ||
| 43 | + api: getAllMenusApi, | ||
| 44 | + }, | ||
| 45 | + // 字段名 | ||
| 46 | + fieldName: 'api', | ||
| 47 | + // 界面显示的label | ||
| 48 | + label: 'ApiSelect', | ||
| 49 | + }, | ||
| 50 | + { | ||
| 51 | + component: 'ApiTreeSelect', | ||
| 52 | + // 对应组件的参数 | ||
| 53 | + componentProps: { | ||
| 54 | + // 菜单接口 | ||
| 55 | + api: getAllMenusApi, | ||
| 56 | + childrenField: 'children', | ||
| 57 | + // 菜单接口转options格式 | ||
| 58 | + labelField: 'name', | ||
| 59 | + valueField: 'path', | ||
| 60 | + }, | ||
| 61 | + // 字段名 | ||
| 62 | + fieldName: 'apiTree', | ||
| 63 | + // 界面显示的label | ||
| 64 | + label: 'ApiTreeSelect', | ||
| 65 | + }, | ||
| 66 | + { | ||
| 67 | + component: 'Input', | ||
| 68 | + fieldName: 'string', | ||
| 69 | + label: 'String', | ||
| 70 | + }, | ||
| 71 | + { | ||
| 72 | + component: 'InputNumber', | ||
| 73 | + fieldName: 'number', | ||
| 74 | + label: 'Number', | ||
| 75 | + }, | ||
| 76 | + { | ||
| 77 | + component: 'RadioGroup', | ||
| 78 | + fieldName: 'radio', | ||
| 79 | + label: 'Radio', | ||
| 80 | + componentProps: { | ||
| 81 | + options: [ | ||
| 82 | + { value: 'A', label: 'A' }, | ||
| 83 | + { value: 'B', label: 'B' }, | ||
| 84 | + { value: 'C', label: 'C' }, | ||
| 85 | + { value: 'D', label: 'D' }, | ||
| 86 | + { value: 'E', label: 'E' }, | ||
| 87 | + ], | ||
| 88 | + }, | ||
| 89 | + }, | ||
| 90 | + { | ||
| 91 | + component: 'RadioGroup', | ||
| 92 | + fieldName: 'radioButton', | ||
| 93 | + label: 'RadioButton', | ||
| 94 | + componentProps: { | ||
| 95 | + isButton: true, | ||
| 96 | + options: ['A', 'B', 'C', 'D', 'E', 'F'].map((v) => ({ | ||
| 97 | + value: v, | ||
| 98 | + label: `选项${v}`, | ||
| 99 | + })), | ||
| 100 | + }, | ||
| 101 | + }, | ||
| 102 | + { | ||
| 103 | + component: 'CheckboxGroup', | ||
| 104 | + fieldName: 'checkbox', | ||
| 105 | + label: 'Checkbox', | ||
| 106 | + componentProps: { | ||
| 107 | + options: ['A', 'B', 'C'].map((v) => ({ value: v, label: `选项${v}` })), | ||
| 108 | + }, | ||
| 109 | + }, | ||
| 110 | + { | ||
| 111 | + component: 'CheckboxGroup', | ||
| 112 | + fieldName: 'checkbox1', | ||
| 113 | + label: 'Checkbox1', | ||
| 114 | + renderComponentContent: () => { | ||
| 115 | + return { | ||
| 116 | + default: () => { | ||
| 117 | + return ['A', 'B', 'C', 'D'].map((v) => | ||
| 118 | + h(ElCheckbox, { label: v, value: v }), | ||
| 119 | + ); | ||
| 120 | + }, | ||
| 121 | + }; | ||
| 122 | + }, | ||
| 123 | + }, | ||
| 124 | + { | ||
| 125 | + component: 'CheckboxGroup', | ||
| 126 | + fieldName: 'checkbotton', | ||
| 127 | + label: 'CheckBotton', | ||
| 128 | + componentProps: { | ||
| 129 | + isButton: true, | ||
| 130 | + options: [ | ||
| 131 | + { value: 'A', label: '选项A' }, | ||
| 132 | + { value: 'B', label: '选项B' }, | ||
| 133 | + { value: 'C', label: '选项C' }, | ||
| 134 | + ], | ||
| 135 | + }, | ||
| 136 | + }, | ||
| 137 | + { | ||
| 138 | + component: 'DatePicker', | ||
| 139 | + fieldName: 'date', | ||
| 140 | + label: 'Date', | ||
| 141 | + }, | ||
| 142 | + { | ||
| 143 | + component: 'Select', | ||
| 144 | + fieldName: 'select', | ||
| 145 | + label: 'Select', | ||
| 146 | + componentProps: { | ||
| 147 | + filterable: true, | ||
| 148 | + options: [ | ||
| 149 | + { value: 'A', label: '选项A' }, | ||
| 150 | + { value: 'B', label: '选项B' }, | ||
| 151 | + { value: 'C', label: '选项C' }, | ||
| 152 | + ], | ||
| 153 | + }, | ||
| 154 | + }, | ||
| 155 | + ], | ||
| 156 | +}); | ||
| 157 | + | ||
| 158 | +const [Drawer, drawerApi] = useVbenDrawer(); | ||
| 159 | +function setFormValues() { | ||
| 160 | + formApi.setValues({ | ||
| 161 | + string: 'string', | ||
| 162 | + number: 123, | ||
| 163 | + radio: 'B', | ||
| 164 | + radioButton: 'C', | ||
| 165 | + checkbox: ['A', 'C'], | ||
| 166 | + checkbotton: ['B', 'C'], | ||
| 167 | + checkbox1: ['A', 'B'], | ||
| 168 | + date: new Date(), | ||
| 169 | + select: 'B', | ||
| 170 | + }); | ||
| 171 | +} | ||
| 172 | +</script> | ||
| 173 | +<template> | ||
| 174 | + <Page | ||
| 175 | + description="我们重新包装了CheckboxGroup、RadioGroup、Select,可以通过options属性传入选项属性数组以自动生成选项" | ||
| 176 | + title="表单演示" | ||
| 177 | + > | ||
| 178 | + <Drawer class="w-[600px]" title="基础表单示例"> | ||
| 179 | + <Form /> | ||
| 180 | + </Drawer> | ||
| 181 | + <ElCard> | ||
| 182 | + <template #header> | ||
| 183 | + <div class="flex items-center"> | ||
| 184 | + <span class="flex-auto">基础表单演示</span> | ||
| 185 | + <ElButton type="primary" @click="setFormValues">设置表单值</ElButton> | ||
| 186 | + </div> | ||
| 187 | + </template> | ||
| 188 | + <ElButton type="primary" @click="drawerApi.open"> 打开抽屉 </ElButton> | ||
| 189 | + </ElCard> | ||
| 190 | + </Page> | ||
| 191 | +</template> |
apps/web-payment/src/views/payment/index.vue
0 → 100644
apps/web-payment/tailwind.config.mjs
0 → 100644
apps/web-payment/tsconfig.json
0 → 100644
| 1 | +++ a/apps/web-payment/tsconfig.json | ||
| 1 | +{ | ||
| 2 | + "$schema": "https://json.schemastore.org/tsconfig", | ||
| 3 | + "extends": "@vben/tsconfig/web-app.json", | ||
| 4 | + "compilerOptions": { | ||
| 5 | + "baseUrl": ".", | ||
| 6 | + "paths": { | ||
| 7 | + "#/*": ["./src/*"] | ||
| 8 | + } | ||
| 9 | + }, | ||
| 10 | + "references": [{ "path": "./tsconfig.node.json" }], | ||
| 11 | + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] | ||
| 12 | +} |
apps/web-payment/tsconfig.node.json
0 → 100644
| 1 | +++ a/apps/web-payment/tsconfig.node.json | ||
| 1 | +{ | ||
| 2 | + "$schema": "https://json.schemastore.org/tsconfig", | ||
| 3 | + "extends": "@vben/tsconfig/node.json", | ||
| 4 | + "compilerOptions": { | ||
| 5 | + "composite": true, | ||
| 6 | + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", | ||
| 7 | + "noEmit": false | ||
| 8 | + }, | ||
| 9 | + "include": ["vite.config.mts"] | ||
| 10 | +} |
apps/web-payment/vite.config.mts
0 → 100644
| 1 | +++ a/apps/web-payment/vite.config.mts | ||
| 1 | +import { defineConfig } from '@vben/vite-config'; | ||
| 2 | + | ||
| 3 | +import ElementPlus from 'unplugin-element-plus/vite'; | ||
| 4 | + | ||
| 5 | +export default defineConfig(async () => { | ||
| 6 | + return { | ||
| 7 | + application: {}, | ||
| 8 | + vite: { | ||
| 9 | + plugins: [ | ||
| 10 | + ElementPlus({ | ||
| 11 | + format: 'esm', | ||
| 12 | + }), | ||
| 13 | + ], | ||
| 14 | + server: { | ||
| 15 | + proxy: { | ||
| 16 | + '/api': { | ||
| 17 | + changeOrigin: true, | ||
| 18 | + rewrite: (path) => path.replace(/^\/api/, ''), | ||
| 19 | + // mock代理目标地址 | ||
| 20 | + target: 'http://localhost:5320/api', | ||
| 21 | + ws: true, | ||
| 22 | + }, | ||
| 23 | + }, | ||
| 24 | + }, | ||
| 25 | + }, | ||
| 26 | + }; | ||
| 27 | +}); |
cspell.json
0 → 100644
| 1 | +++ a/cspell.json | ||
| 1 | +{ | ||
| 2 | + "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", | ||
| 3 | + "version": "0.2", | ||
| 4 | + "language": "en,en-US", | ||
| 5 | + "allowCompoundWords": true, | ||
| 6 | + "words": [ | ||
| 7 | + "acmr", | ||
| 8 | + "antd", | ||
| 9 | + "antdv", | ||
| 10 | + "astro", | ||
| 11 | + "brotli", | ||
| 12 | + "clsx", | ||
| 13 | + "defu", | ||
| 14 | + "demi", | ||
| 15 | + "echarts", | ||
| 16 | + "ependencies", | ||
| 17 | + "esno", | ||
| 18 | + "etag", | ||
| 19 | + "execa", | ||
| 20 | + "iconify", | ||
| 21 | + "iconoir", | ||
| 22 | + "intlify", | ||
| 23 | + "lockb", | ||
| 24 | + "lucide", | ||
| 25 | + "minh", | ||
| 26 | + "minw", | ||
| 27 | + "mkdist", | ||
| 28 | + "mockjs", | ||
| 29 | + "naiveui", | ||
| 30 | + "nocheck", | ||
| 31 | + "noopener", | ||
| 32 | + "noreferrer", | ||
| 33 | + "nprogress", | ||
| 34 | + "nuxt", | ||
| 35 | + "pinia", | ||
| 36 | + "prefixs", | ||
| 37 | + "publint", | ||
| 38 | + "qrcode", | ||
| 39 | + "reka", | ||
| 40 | + "shadcn", | ||
| 41 | + "sonner", | ||
| 42 | + "sortablejs", | ||
| 43 | + "styl", | ||
| 44 | + "taze", | ||
| 45 | + "tdesign", | ||
| 46 | + "ui-kit", | ||
| 47 | + "uicons", | ||
| 48 | + "unplugin", | ||
| 49 | + "unref", | ||
| 50 | + "vben", | ||
| 51 | + "vbenjs", | ||
| 52 | + "vite", | ||
| 53 | + "vitejs", | ||
| 54 | + "vitepress", | ||
| 55 | + "vnode", | ||
| 56 | + "vueuse", | ||
| 57 | + "yxxx" | ||
| 58 | + ], | ||
| 59 | + "ignorePaths": [ | ||
| 60 | + "**/node_modules/**", | ||
| 61 | + "**/dist/**", | ||
| 62 | + "**/*-dist/**", | ||
| 63 | + "**/icons/**", | ||
| 64 | + "pnpm-lock.yaml", | ||
| 65 | + "**/*.log", | ||
| 66 | + "**/*.test.ts", | ||
| 67 | + "**/*.spec.ts", | ||
| 68 | + "**/__tests__/**" | ||
| 69 | + ] | ||
| 70 | +} |
docs/.vitepress/components/demo-preview.vue
0 → 100644
| 1 | +++ a/docs/.vitepress/components/demo-preview.vue | ||
| 1 | +<script setup lang="ts"> | ||
| 2 | +import { computed } from 'vue'; | ||
| 3 | + | ||
| 4 | +import PreviewGroup from './preview-group.vue'; | ||
| 5 | + | ||
| 6 | +interface Props { | ||
| 7 | + files?: string; | ||
| 8 | +} | ||
| 9 | + | ||
| 10 | +const props = withDefaults(defineProps<Props>(), { files: '() => []' }); | ||
| 11 | + | ||
| 12 | +const parsedFiles = computed(() => { | ||
| 13 | + try { | ||
| 14 | + return JSON.parse(decodeURIComponent(props.files ?? '')); | ||
| 15 | + } catch { | ||
| 16 | + return []; | ||
| 17 | + } | ||
| 18 | +}); | ||
| 19 | +</script> | ||
| 20 | + | ||
| 21 | +<template> | ||
| 22 | + <div class="relative rounded-xl border border-border shadow-float"> | ||
| 23 | + <div | ||
| 24 | + class="not-prose relative w-full overflow-x-auto rounded-t-lg px-4 py-6" | ||
| 25 | + > | ||
| 26 | + <div class="flex w-full max-w-[700px] px-2"> | ||
| 27 | + <ClientOnly> | ||
| 28 | + <slot v-if="parsedFiles.length > 0"></slot> | ||
| 29 | + <div v-else class="text-sm text-destructive"> | ||
| 30 | + <span class="rounded-sm bg-destructive px-1 py-1 text-foreground"> | ||
| 31 | + ERROR: | ||
| 32 | + </span> | ||
| 33 | + The preview directory does not exist. Please check the 'dir' | ||
| 34 | + parameter. | ||
| 35 | + </div> | ||
| 36 | + </ClientOnly> | ||
| 37 | + </div> | ||
| 38 | + </div> | ||
| 39 | + <PreviewGroup v-if="parsedFiles.length > 0" :files="parsedFiles"> | ||
| 40 | + <template v-for="file in parsedFiles" #[file]> | ||
| 41 | + <slot :name="file"></slot> | ||
| 42 | + </template> | ||
| 43 | + </PreviewGroup> | ||
| 44 | + </div> | ||
| 45 | +</template> |
docs/.vitepress/components/index.ts
0 → 100644
docs/.vitepress/components/preview-group.vue
0 → 100644
| 1 | +++ a/docs/.vitepress/components/preview-group.vue | ||
| 1 | +<script setup lang="ts"> | ||
| 2 | +import type { SetupContext } from 'vue'; | ||
| 3 | + | ||
| 4 | +import { computed, ref, useSlots } from 'vue'; | ||
| 5 | + | ||
| 6 | +import { VbenTooltip } from '@vben-core/shadcn-ui'; | ||
| 7 | + | ||
| 8 | +import { Code } from 'lucide-vue-next'; | ||
| 9 | +import { | ||
| 10 | + TabsContent, | ||
| 11 | + TabsIndicator, | ||
| 12 | + TabsList, | ||
| 13 | + TabsRoot, | ||
| 14 | + TabsTrigger, | ||
| 15 | +} from 'reka-ui'; | ||
| 16 | + | ||
| 17 | +defineOptions({ | ||
| 18 | + inheritAttrs: false, | ||
| 19 | +}); | ||
| 20 | + | ||
| 21 | +const props = withDefaults( | ||
| 22 | + defineProps<{ | ||
| 23 | + files?: string[]; | ||
| 24 | + }>(), | ||
| 25 | + { files: () => [] }, | ||
| 26 | +); | ||
| 27 | + | ||
| 28 | +const open = ref(false); | ||
| 29 | + | ||
| 30 | +const slots: SetupContext['slots'] = useSlots(); | ||
| 31 | + | ||
| 32 | +const tabs = computed(() => { | ||
| 33 | + return props.files.map((file) => { | ||
| 34 | + return { | ||
| 35 | + component: slots[file], | ||
| 36 | + label: file, | ||
| 37 | + }; | ||
| 38 | + }); | ||
| 39 | +}); | ||
| 40 | + | ||
| 41 | +const currentTab = ref('index.vue'); | ||
| 42 | + | ||
| 43 | +const toggleOpen = () => { | ||
| 44 | + open.value = !open.value; | ||
| 45 | +}; | ||
| 46 | +</script> | ||
| 47 | + | ||
| 48 | +<template> | ||
| 49 | + <TabsRoot | ||
| 50 | + v-model="currentTab" | ||
| 51 | + class="overflow-hidden rounded-b-xl border-t border-border bg-background-deep" | ||
| 52 | + @update:model-value="open = true" | ||
| 53 | + > | ||
| 54 | + <div class="flex border-b-2 border-border bg-background pr-2"> | ||
| 55 | + <div class="flex w-full items-center justify-between text-[13px]"> | ||
| 56 | + <TabsList class="relative flex"> | ||
| 57 | + <template v-if="open"> | ||
| 58 | + <TabsIndicator | ||
| 59 | + class="absolute bottom-0 left-0 h-[2px] w-[--reka-tabs-indicator-size] translate-x-[--reka-tabs-indicator-position] rounded-full transition-[width,transform] duration-300" | ||
| 60 | + > | ||
| 61 | + <div class="size-full bg-[var(--vp-c-indigo-1)]"></div> | ||
| 62 | + </TabsIndicator> | ||
| 63 | + <TabsTrigger | ||
| 64 | + v-for="(tab, index) in tabs" | ||
| 65 | + :key="index" | ||
| 66 | + :value="tab.label" | ||
| 67 | + class="border-box px-4 py-3 text-foreground data-[state=active]:text-[var(--vp-c-indigo-1)]" | ||
| 68 | + tabindex="-1" | ||
| 69 | + > | ||
| 70 | + {{ tab.label }} | ||
| 71 | + </TabsTrigger> | ||
| 72 | + </template> | ||
| 73 | + </TabsList> | ||
| 74 | + | ||
| 75 | + <div | ||
| 76 | + :class="{ | ||
| 77 | + 'py-2': !open, | ||
| 78 | + }" | ||
| 79 | + class="flex items-center" | ||
| 80 | + > | ||
| 81 | + <VbenTooltip side="top"> | ||
| 82 | + <template #trigger> | ||
| 83 | + <Code | ||
| 84 | + class="size-7 cursor-pointer rounded-full p-1.5 hover:bg-accent" | ||
| 85 | + @click="toggleOpen" | ||
| 86 | + /> | ||
| 87 | + </template> | ||
| 88 | + {{ open ? 'Collapse code' : 'Expand code' }} | ||
| 89 | + </VbenTooltip> | ||
| 90 | + </div> | ||
| 91 | + </div> | ||
| 92 | + </div> | ||
| 93 | + <div | ||
| 94 | + :class="`${open ? 'h-[unset] max-h-[80vh]' : 'h-0'}`" | ||
| 95 | + class="block overflow-y-scroll bg-[var(--vp-code-block-bg)] transition-all duration-300" | ||
| 96 | + > | ||
| 97 | + <TabsContent | ||
| 98 | + v-for="tab in tabs" | ||
| 99 | + :key="tab.label" | ||
| 100 | + :value="tab.label" | ||
| 101 | + as-child | ||
| 102 | + class="rounded-xl" | ||
| 103 | + > | ||
| 104 | + <div class="relative rounded-xl text-foreground"> | ||
| 105 | + <component :is="tab.component" class="border-0" /> | ||
| 106 | + </div> | ||
| 107 | + </TabsContent> | ||
| 108 | + </div> | ||
| 109 | + </TabsRoot> | ||
| 110 | +</template> |