Compare commits

..

33 Commits

Author SHA1 Message Date
Soybean
db3c25ea14 chore(release): 0.1.1 精简版发布 2022-01-20 21:32:16 +08:00
Soybean
5eddb4910c docs(projects): update README.md 2022-01-20 21:29:30 +08:00
Soybean
ce531ce5dd feat(projects): 细节完善、迁移页面 2022-01-20 21:24:01 +08:00
Soybean
28efbdbc70 feat(projects): 迁移多页签 2022-01-20 02:59:29 +08:00
Soybean
cc290accc2 feat(projects): 细节完善 2022-01-19 00:36:53 +08:00
Soybean
579e07400e fix(projects): 修复vertical-mix布局、重构初始化的loading 2022-01-18 03:21:02 +08:00
Soybean
b2a4ddf5e3 refactor(projects): 恢复pinia默认写法 2022-01-17 03:42:16 +08:00
Soybean
28b5d22401 fix(projects): 修复面包屑数据 2022-01-14 14:17:34 +08:00
Soybean
839b82ba8b feat(projects): 请求拦截器添加刷新token 2022-01-12 19:53:45 +08:00
Soybean
09c7658c21 feat(projects): 面包屑 2022-01-11 19:03:42 +08:00
Soybean
e25afe2fad feat(projects): 添加侧边菜单 2022-01-11 08:22:31 +08:00
Soybean
371fad4f26 build(projects): 添加vercel打包的环境 2022-01-10 19:24:13 +08:00
Soybean
a090d398fc feat(projects): 添加头部折叠按钮 2022-01-10 12:43:04 +08:00
Soybean
6d132c5977 feat(projects): 主题配置抽屉: 迁移其他功能 2022-01-09 13:25:42 +08:00
Soybean
912bfdf439 feat(projects): 主题配置抽屉:迁移暗黑模式、布局模式、添加颜色选择面板 2022-01-09 12:24:39 +08:00
Soybean
bf020a8258 feat(projects): theme store完成 2022-01-08 20:49:21 +08:00
Soybean
10e4d81bd6 feat(projects): 添加抽屉 2022-01-07 18:51:06 +08:00
Soybean
0653fb144f feat(projects): 创建自定义布局组件SoybeanLayout 2022-01-07 15:44:28 +08:00
Soybean
006467a062 feat(projects): 新增BasicLayout布局 2022-01-07 02:50:50 +08:00
Soybean
0c5770dfd2 build(projects): 修改vscode配置 2022-01-06 14:40:07 +08:00
Soybean
0e783bcf7b fix(projects): 去除Layout组件冗余代码 2022-01-06 11:38:46 +08:00
Soybean
e5793e1c8d style(projects): 路由相关文件夹简化 2022-01-06 11:37:06 +08:00
Soybean
85b55bb37a feat(projects): 多级路由的所有子路由转换成二级路由 2022-01-06 11:30:02 +08:00
Soybean
b36a62b150 refactor(projects): 单独路由逻辑重构、路由转换函数优化 2022-01-06 02:42:00 +08:00
Soybean
c804b21ceb feat(projects): 添加NaiveProvider组件 2022-01-05 19:06:23 +08:00
Soybean
5bfb8199b4 fix(projects): 修复redirect-not-found子路由 2022-01-05 13:49:42 +08:00
Soybean
ab9a6a2f39 refactor(projects): 单独一级路由相关逻辑重构 2022-01-05 11:56:28 +08:00
Soybean
b93b80cb4b feat(projects): 迁移登录完成 2022-01-05 01:36:16 +08:00
Soybean
f5a36a05cb feat(projects): 登录页面开始迁移 2022-01-04 19:09:00 +08:00
Soybean
035fa114c9 feat(projects): 初始化加载效果:应用主题颜色 2022-01-04 14:05:18 +08:00
Soybean
2c196841bd feat(projects): 集成naiveUI主题配置,将css vars添加至html 2022-01-04 02:20:32 +08:00
Soybean
0d2a5629e8 feat(projects): 路由页面跳转权限完成 2022-01-04 00:00:48 +08:00
Soybean
de2057f141 refactor(projects): 精简版+动态路由权限初步 2022-01-03 22:20:10 +08:00
374 changed files with 7484 additions and 13636 deletions

8
.env
View File

@@ -1,9 +1,7 @@
# 变量需要以VITE开头
VITE_BASE_URL=/
BASE_URL=/
VITE_APP_NAME=SoybeanAdmin
VITE_APP_TITLE=SoybeanAdmin
VITE_APP_TITLE=Soybean管理系统
VITE_APP_DESC=中后台管理系统模版
VITE_APP_DESC=SoybeanAdmin是一个中后台管理系统模版

21
.env-config.ts Normal file
View File

@@ -0,0 +1,21 @@
/** 请求环境配置 */
type ServiceEnv = {
[key in Service.HttpEnv]: {
/** 请求环境 */
env: Service.HttpEnv;
/** 请求地址 */
url: string;
};
};
/** 请求的环境 */
export const serviceEnv: ServiceEnv = {
test: {
env: 'test',
url: 'http://120.76.42.91:18888'
},
prod: {
env: 'prod',
url: 'http://120.76.42.91:18888'
}
};

View File

@@ -1,5 +0,0 @@
#请求的环境
VITE_HTTP_ENV=DEV
#请求地址
VITE_HTTP_URL=https://test.aisuit.com.cn

View File

@@ -1,5 +0,0 @@
#请求的环境 正式环境
VITE_HTTP_ENV=PROD
#请求地址
VITE_HTTP_URL=http://192.168.100.43:8201

View File

@@ -1,4 +0,0 @@
VITE_HTTP_ENV=STAGING
#请求地址
VITE_HTTP_URL=http://192.168.100.43:8201

View File

@@ -11,4 +11,4 @@ lib
/docs
.vscode
.local
index.html
!.env-config.ts

View File

@@ -22,25 +22,141 @@ module.exports = {
plugins: ['vue', '@typescript-eslint'],
extends: ['plugin:vue/vue3-recommended', 'airbnb-base', '@vue/typescript/recommended', 'plugin:prettier/recommended'],
rules: {
'no-unused-vars': 'off',
'import/extensions': 'off',
'import/no-extraneous-dependencies': 'off',
'import/no-unresolved': 0,
'no-shadow': 0,
'import/order': [
'error',
{
'newlines-between': 'never',
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
pathGroups: [
{
pattern: 'vue',
group: 'external',
position: 'before'
},
{
pattern: 'vue-router',
group: 'external',
position: 'before'
},
{
pattern: 'pinia',
group: 'external',
position: 'before'
},
{
pattern: 'naive-ui',
group: 'external',
position: 'before'
},
{
pattern: '@/config',
group: 'internal',
position: 'before'
},
{
pattern: '@/settings',
group: 'internal',
position: 'before'
},
{
pattern: '@/enum',
group: 'internal',
position: 'before'
},
{
pattern: '@/plugins',
group: 'internal',
position: 'before'
},
{
pattern: '@/layouts',
group: 'internal',
position: 'before'
},
{
pattern: '@/layouts',
group: 'internal',
position: 'before'
},
{
pattern: '@/views',
group: 'internal',
position: 'before'
},
{
pattern: '@/components',
group: 'internal',
position: 'before'
},
{
pattern: '@/router',
group: 'internal',
position: 'before'
},
{
pattern: '@/store',
group: 'internal',
position: 'before'
},
{
pattern: '@/composables',
group: 'internal',
position: 'before'
},
{
pattern: '@/hooks',
group: 'internal',
position: 'before'
},
{
pattern: '@/service',
group: 'internal',
position: 'before'
},
{
pattern: '@/utils',
group: 'internal',
position: 'before'
},
{
pattern: '@/assets',
group: 'internal',
position: 'before'
},
{
pattern: '@/**',
group: 'internal',
position: 'before'
},
{
pattern: '@/interface',
group: 'internal',
position: 'before'
}
],
pathGroupsExcludedImportTypes: ['vue', 'vue-router', 'pinia', 'naive-ui']
}
],
'import/prefer-default-export': 0,
'no-use-before-define': 'off',
'vue/multi-word-component-names': 0,
'max-classes-per-file': 0,
'no-shadow': 0,
'no-unused-vars': 'off',
'no-use-before-define': 'off',
'vue/comment-directive': 0,
'vue/multi-word-component-names': 0,
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-explicit-any': 0,
'@typescript-eslint/no-inferrable-types': 0,
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-var-requires': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-unused-vars': ['warn', { ignoreRestSiblings: true }],
'@typescript-eslint/no-use-before-define': ['error', { classes: true, functions: false, typedefs: false }]
'@typescript-eslint/no-use-before-define': ['error', { classes: true, functions: false, typedefs: false }],
'@typescript-eslint/no-var-requires': 'off'
}
};

20
.gitignore vendored
View File

@@ -1,7 +1,27 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
.DS_Store
dist
dist-ssr
*.local
/cypress/videos/
/cypress/screenshots/
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
stats.html

1
.husky/.gitignore vendored
View File

@@ -1 +0,0 @@
_

View File

@@ -1,4 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
pnpm lint:fix && pnpm vtsc
pnpm lint && pnpm typecheck

View File

@@ -15,5 +15,13 @@ module.exports = {
requireConfig: false, // Require a 'prettierconfig' to format prettier
stylelintIntegration: false, //不让prettier使用stylelint的代码格式进行校验
trailingComma: 'none', // 在对象或数组最后一个元素后面是否加逗号在ES5中加尾逗号
tslintIntegration: false // 不让prettier使用tslint的代码格式进行校验
}
tslintIntegration: false, // 不让prettier使用tslint的代码格式进行校验
overrides: [
{
files: '*.html',
options: {
parser: 'html'
}
}
]
};

View File

@@ -4,7 +4,8 @@
"formulahendry.auto-complete-tag",
"steoates.autoimport",
"formulahendry.auto-rename-tag",
"coenraads.bracket-pair-colorizer",
"coenraads.bracket-pair-colorizer-2",
"naumovs.color-highlight",
"pranaygp.vscode-css-peek",
"mikestead.dotenv",
"editorconfig.editorconfig",

View File

@@ -9,6 +9,8 @@
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": "active",
"git.enableSmartCommit": true,
"path-intellisense.mappings": {
"@": "${workspaceFolder}/src",
@@ -54,14 +56,15 @@
"[markdown]": {
"editor.defaultFormatter": "yzhang.markdown-all-in-one"
},
"workbench.productIconTheme": "fluent-icons",
"vue3snippets.enable-compile-vue-file-on-did-save-code": false,
"editor.formatOnSave": false,
"material-icon-theme.activeIconPack": "angular",
"material-icon-theme.files.associations": {},
"material-icon-theme.folders.associations": {
"enum": "typescript",
"enums": "typescript",
"store": "context",
"stores": "context",
"composable": "hook",
"composables": "hook",
"directive": "tools",

View File

@@ -2,140 +2,49 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.0.5](https://github.com/honghuangdc/soybean-admin/compare/v0.0.4...v0.0.5) (2021-11-28)
### [0.1.1](https://github.com/honghuangdc/soybean-admin/compare/v0.0.5...v0.1.1) (2022-01-20)
### Features
* **projects:** 新增组件页面:按钮、卡片示例 ([bdc39af](https://github.com/honghuangdc/soybean-admin/commit/bdc39aff1b05845cbcfcab8d40620d7b8ba52d13))
* **projects:** theme store完成 ([bf020a8](https://github.com/honghuangdc/soybean-admin/commit/bf020a82580e6b1fbda1cc1e0bd6176770434884))
* **projects:** 主题配置抽屉: 迁移其他功能 ([6d132c5](https://github.com/honghuangdc/soybean-admin/commit/6d132c59770e925cfc61217dcefa5b4d937604df))
* **projects:** 主题配置抽屉:迁移暗黑模式、布局模式、添加颜色选择面板 ([912bfdf](https://github.com/honghuangdc/soybean-admin/commit/912bfdf4390ab624d3f8e343be88e8c1cf7ab5b6))
* **projects:** 创建自定义布局组件SoybeanLayout ([0653fb1](https://github.com/honghuangdc/soybean-admin/commit/0653fb144fe9d49f24ef4fe6e4a58de6de342b78))
* **projects:** 初始化加载效果:应用主题颜色 ([035fa11](https://github.com/honghuangdc/soybean-admin/commit/035fa114c9fd638cf467e6a73a8e4c558f503deb))
* **projects:** 图标选择器增加扩展树形 ([041012b](https://github.com/honghuangdc/soybean-admin/commit/041012b3ee04d960c1e38895839225613f7af377))
* **projects:** 增加Icon选择器组件 ([9472b51](https://github.com/honghuangdc/soybean-admin/commit/9472b51811f419e9139de81c73f2c71d170700c2))
* **projects:** 增加全局搜索菜单功能 ([b9ce691](https://github.com/honghuangdc/soybean-admin/commit/b9ce69130b12712013228326f883e2d973e4e46a))
* **projects:** 增加项目文档外链 ([1901a0b](https://github.com/honghuangdc/soybean-admin/commit/1901a0bfb7bfa516dfda552675397ddec96b8d4b))
* **projects:** 多级路由的所有子路由转换成二级路由 ([85b55bb](https://github.com/honghuangdc/soybean-admin/commit/85b55bb37a0a06e2645b96ed81aefe463127121a))
* **projects:** 引入mockjs ([9bc682d](https://github.com/honghuangdc/soybean-admin/commit/9bc682dae878c084e38a0e2c9a4a2de171023c48))
* **projects:** 新增BasicLayout布局 ([006467a](https://github.com/honghuangdc/soybean-admin/commit/006467a0626f427da3f516d90c15bf1e1eef0e55))
* **projects:** 添加cryptojs对本地缓存数据进行加密 ([7a0648d](https://github.com/honghuangdc/soybean-admin/commit/7a0648dba55a98f61f4d81696307d86c82a1d34d))
* **projects:** 添加NaiveProvider组件 ([c804b21](https://github.com/honghuangdc/soybean-admin/commit/c804b21ceb92133c6ea7cc64c87521cc164e40ce))
* **projects:** 添加侧边菜单 ([e25afe2](https://github.com/honghuangdc/soybean-admin/commit/e25afe2fadfe86b9330ee02190a4e40b8321714c))
* **projects:** 添加头部折叠按钮 ([a090d39](https://github.com/honghuangdc/soybean-admin/commit/a090d398fc071e246b92d0da80883cf5cbedba0e))
* **projects:** 添加常用组件、composables函数 ([230a50a](https://github.com/honghuangdc/soybean-admin/commit/230a50a4cf4d2ebb62b19d6324234243cf6b2f0d))
* **projects:** 添加抽屉 ([10e4d81](https://github.com/honghuangdc/soybean-admin/commit/10e4d81bd6a0b35d8cfb4f7a1e981f8ef6ab87cc))
* **projects:** 添加表格页面示例 ([51c744c](https://github.com/honghuangdc/soybean-admin/commit/51c744c8e2c8ed9691e92e35b6a88582f22c30d8))
* **projects:** 添加路由跳转浏览器新标签 ([987cef3](https://github.com/honghuangdc/soybean-admin/commit/987cef336338987f2e6f0d5aba8f6d4602b297ca))
* **projects:** 登录页面开始迁移 ([f5a36a0](https://github.com/honghuangdc/soybean-admin/commit/f5a36a05cb626ec62115283f1d2c534b2a787bdd))
* **projects:** 细节完善 ([cc290ac](https://github.com/honghuangdc/soybean-admin/commit/cc290accc29282e9ba655356e2695b6ca4b23605))
* **projects:** 细节完善、迁移页面 ([ce531ce](https://github.com/honghuangdc/soybean-admin/commit/ce531ce5dda0b4a1024aa6bd3d68835b59760d57))
* **projects:** 菜单搜索增加大小写转换 ([2907868](https://github.com/honghuangdc/soybean-admin/commit/29078689b0652cf4ae852c93d8601a157579adcc))
* **projects:** 请求拦截器添加刷新token ([839b82b](https://github.com/honghuangdc/soybean-admin/commit/839b82ba8b052b02e24bcfe6da54160609a4fd4b))
* **projects:** 路由页面跳转权限完成 ([0d2a562](https://github.com/honghuangdc/soybean-admin/commit/0d2a5629e89c73a32d6c79f04b51543e1513e006))
* **projects:** 迁移多页签 ([28efbdb](https://github.com/honghuangdc/soybean-admin/commit/28efbdbc70733d22011a0eee084d35711429d188))
* **projects:** 迁移登录完成 ([b93b80c](https://github.com/honghuangdc/soybean-admin/commit/b93b80cb4b35268dfb6a09517a2494af24748dac))
* **projects:** 集成naiveUI主题配置将css vars添加至html ([2c19684](https://github.com/honghuangdc/soybean-admin/commit/2c196841bd8527d7acccefe6a7545e0a49d532f7))
* **projects:** 面包屑 ([09c7658](https://github.com/honghuangdc/soybean-admin/commit/09c7658c21c7dda461dbb528e85b638b5a7dfacd))
### Bug Fixes
* **components:** 修复HorizontalLayout布局 ([9fb641f](https://github.com/honghuangdc/soybean-admin/commit/9fb641f71e74e054c84cda8e18969d1168ef2903))
### [0.0.4](https://github.com/honghuangdc/soybean-admin/compare/v0.0.3...v0.0.4) (2021-11-25)
### Features
* **components:** 添加多页签Tab点击后自动往中间滚动 ([8ce627a](https://github.com/honghuangdc/soybean-admin/commit/8ce627a397ee2605d967e7f9c8aa558b99fca22d))
* **projects:** 新增网址导航页面 ([32aa5ee](https://github.com/honghuangdc/soybean-admin/commit/32aa5ee75af80c2f959b74573d5c44c452d2715c))
* **storage:** local存储增加有效期 ([e6c9b35](https://github.com/honghuangdc/soybean-admin/commit/e6c9b35ab402df7d9ebb82306131fc30d0a8b893))
### Bug Fixes
* **components:** 修复多页签按钮风格的tab滚动问题 ([c429cd0](https://github.com/honghuangdc/soybean-admin/commit/c429cd0293dbfbf6b6df539857c276d3218de754))
* **components:** 修复多页签Tab自动滚动问题 ([20aa39f](https://github.com/honghuangdc/soybean-admin/commit/20aa39f14ed0239f02118b62a6aa4706b1f9dd21))
* **projects:** 添加西瓜视频实例在onUnMounted的销毁多页签居中距离精确 ([738964a](https://github.com/honghuangdc/soybean-admin/commit/738964a76975dc3cb1f3206eb13a7612b92f1aed))
* **projects:** 修复打包构建时图标错误 ([93f9aa9](https://github.com/honghuangdc/soybean-admin/commit/93f9aa9584be803704cf0ff54c8da0f84b71c408))
* **types:** 添加dotEnv类型的非空判断 ([cff11d9](https://github.com/honghuangdc/soybean-admin/commit/cff11d91758a470ad6ff33888ed24747b2cc0c03))
### [0.0.3](https://github.com/honghuangdc/soybean-admin/compare/v0.0.2...v0.0.3) (2021-11-23)
### 0.0.2 (2021-11-21)
### Features
* **component:** 增加剪贴板示例 ([f1cd995](https://github.com/honghuangdc/soybean-admin/commit/f1cd9955d9ef0dd06e6eb0ab88ab6be80db789a3))
* **components:** 添加面包屑 ([c1cdc3a](https://github.com/honghuangdc/soybean-admin/commit/c1cdc3a9ed673d0fd84aa1eaa9fc72468bd5aaf9))
* **components:** 添加图片验证码 ([336c776](https://github.com/honghuangdc/soybean-admin/commit/336c7766f9130619b7076e832d7ade7cbc3049f2))
* **components:** 添加主题配置抽屉,添加暗黑主题 ([a87593f](https://github.com/honghuangdc/soybean-admin/commit/a87593f58a1185d6360b8e49ffe1c9fff768770e))
* **components:** 添加vertical-mix的导航模式下的菜单 ([f24ec1c](https://github.com/honghuangdc/soybean-admin/commit/f24ec1c5326c117e618aed8b3e1867c24fcd84f4))
* **projects:** 布局调整 ([eda87f0](https://github.com/honghuangdc/soybean-admin/commit/eda87f041d5d87ae9612f369608e486a8e563f17))
* **projects:** 菜单数据及组件接入 ([3226a72](https://github.com/honghuangdc/soybean-admin/commit/3226a724be65935ce89fe6ae67f49a20d255c6ac))
* **projects:** 导航栏模式配置:界面实现及主题配置布局调整 ([f002124](https://github.com/honghuangdc/soybean-admin/commit/f002124ee11bc93e6b9955549143b695417e7f8d))
* **projects:** 登录页面实现 ([f1e7cf6](https://github.com/honghuangdc/soybean-admin/commit/f1e7cf608ea7d61dcd24f8780cde9cc4c59658ce))
* **projects:** 多页签绑定路由 ([f29bc05](https://github.com/honghuangdc/soybean-admin/commit/f29bc05dd9f53144ef56440033a6f747c112e83d))
* **projects:** 分析页更新,添加关于页面 ([8e18218](https://github.com/honghuangdc/soybean-admin/commit/8e18218196c52e6a34b96bc313044b6e47886f85))
* **projects:** 工作台页面:添加技术栈官网链接 ([364c64b](https://github.com/honghuangdc/soybean-admin/commit/364c64b4641e48bcf8cc8600680bcaa39a1a9413))
* **projects:** 工作台页面布局 ([4c85569](https://github.com/honghuangdc/soybean-admin/commit/4c85569b764b176c9c3a7f9ba3092ff3567e5512))
* **projects:** 首页更新 ([5c01006](https://github.com/honghuangdc/soybean-admin/commit/5c01006306873944671a4f1d863ced6ba23f6245))
* **projects:** 四种基本布局完成 ([86d4a20](https://github.com/honghuangdc/soybean-admin/commit/86d4a207eef8daf01c6336e8aaedf3aebb90e7a7))
* **projects:** 添加百度地图插件 ([6abe094](https://github.com/honghuangdc/soybean-admin/commit/6abe094ff23f52fdd62c025bce17debd9ea2f907))
* **projects:** 添加多级菜单页面 ([3f49d6d](https://github.com/honghuangdc/soybean-admin/commit/3f49d6db30aee0a6c1007cb00069835b102deb70))
* **projects:** 添加多页签风格:按钮和浏览器两种风格 ([3cfa0f1](https://github.com/honghuangdc/soybean-admin/commit/3cfa0f103cf788e57ee26743e89bf5fe33a09660))
* **projects:** 添加多页签右键菜单 ([d6f5237](https://github.com/honghuangdc/soybean-admin/commit/d6f5237c8c167314d578312dcad7505737f0b4c8))
* **projects:** 添加富文本和markdown编辑器插件及示例页面 ([60c2064](https://github.com/honghuangdc/soybean-admin/commit/60c20647a0d8e6d877a0f23a6e7da05ff09d14a0))
* **projects:** 添加固定路由 ([ff4a09c](https://github.com/honghuangdc/soybean-admin/commit/ff4a09c452c98791f7d67ba5f135e9cf5099c29c))
* **projects:** 添加环境文件env对应的类型 ([4f05095](https://github.com/honghuangdc/soybean-admin/commit/4f050953363b364815a08103047df3fe377d8f56))
* **projects:** 添加判断是否是移动端的hooks ([0a9fba9](https://github.com/honghuangdc/soybean-admin/commit/0a9fba90b5e51fd2d39c47490f49dac7599a9742))
* **projects:** 添加全屏显示 ([0a1711d](https://github.com/honghuangdc/soybean-admin/commit/0a1711d5b1d8e863d24a55690fa8696c79acaaf9))
* **projects:** 添加项目配置拷贝 ([2d9d5c0](https://github.com/honghuangdc/soybean-admin/commit/2d9d5c0353ca6d2dc86965fe383bf2925a47d239))
* **projects:** 添加exception页面403404500 ([d012c4e](https://github.com/honghuangdc/soybean-admin/commit/d012c4ecf2cd325567d419684153955560ce90da))
* **projects:** 添加multiTab标签页 ([eec0b36](https://github.com/honghuangdc/soybean-admin/commit/eec0b36f594e0d337f13d3d0ce30b1f768614f5c))
* **projects:** 添加reload context ([03ebd49](https://github.com/honghuangdc/soybean-admin/commit/03ebd49c8639bf7f4f88b1a0523d2caec2d248ee))
* **projects:** 添加svg logo自适应主题颜色 ([e1e5579](https://github.com/honghuangdc/soybean-admin/commit/e1e5579e8fe71ed97e2ce11d907705157874bd71))
* **projects:** 添加swiper插件 ([27f600c](https://github.com/honghuangdc/soybean-admin/commit/27f600c4677afeacd3e67f189df139db5cde0aa3))
* **projects:** 头部添加菜单折叠按钮和github地址 ([3ec1fc8](https://github.com/honghuangdc/soybean-admin/commit/3ec1fc8f0c23fcba56d4bffb20028948f985659c))
* **projects:** 项目初始化搭建集成eslint规范集成代码提交规范 ([6754da4](https://github.com/honghuangdc/soybean-admin/commit/6754da4d83976a02eced801220320d8c9aa1da85))
* **projects:** 新增导航模式配置 ([49c2dc4](https://github.com/honghuangdc/soybean-admin/commit/49c2dc4f23913c9ef86ee046c6ae53d4406cbca7))
* **projects:** 新增顶部菜单 ([221d2cc](https://github.com/honghuangdc/soybean-admin/commit/221d2cc02dfdf3f78cb415f26c88f1f274942222))
* **projects:** 新增多页签缓存功能 ([d86f891](https://github.com/honghuangdc/soybean-admin/commit/d86f891c64f802bbca50e31e3e4f7ccdad65eed1))
* **projects:** 新增高德地图插件 ([ea82edc](https://github.com/honghuangdc/soybean-admin/commit/ea82edc1146fefa208bb9e6f985dfb000d197d16))
* **projects:** 新增视频插件 ([6a692d4](https://github.com/honghuangdc/soybean-admin/commit/6a692d4f99942389cd2a5e72ebc852a92e80f742))
* **projects:** 新增腾讯地图插件 ([3f02c21](https://github.com/honghuangdc/soybean-admin/commit/3f02c215c54fde4c85bf13e92c2620553d5a1840))
* **projects:** 新增文档页面 ([7654b2a](https://github.com/honghuangdc/soybean-admin/commit/7654b2adf3d0bf051d13b401dfa3534ca7ee3e0c))
* **projects:** 新增主题配置 ([ed67b79](https://github.com/honghuangdc/soybean-admin/commit/ed67b797c215fe165808505f4b0b9400f3182383))
* **projects:** 新增主题配置:页面功能 ([8601ce2](https://github.com/honghuangdc/soybean-admin/commit/8601ce2ea184455fcba1d17d759cd4b933b31d96))
* **projects:** 新增主题颜色配置 ([d93493b](https://github.com/honghuangdc/soybean-admin/commit/d93493b91ca856573c306e890e8c6f6a46b5bda3))
* **projects:** 增加Icon以及打印功能示例 ([d5bce26](https://github.com/honghuangdc/soybean-admin/commit/d5bce26454c7d7c9da29e01675624f985755779f))
* **projects:** 主题配置:页面功能和页面显示 ([a0392b3](https://github.com/honghuangdc/soybean-admin/commit/a0392b3d28f89f2b5fcf5b4d2b82ab7a068a23b8))
* **projects:** vertical-mix的导航模式的二级菜单显示 ([736f314](https://github.com/honghuangdc/soybean-admin/commit/736f3146cb7cb3f56e06a8185ec8532f25c40b13))
* **route:** 增加功能示例模块 ([efd29bc](https://github.com/honghuangdc/soybean-admin/commit/efd29bc331f630b57eab800bba08b22c53115d76))
### Bug Fixes
* **多页签:** 在pc模式下右键某个多页签会切换路由 ([a4394dc](https://github.com/honghuangdc/soybean-admin/commit/a4394dc3ee81ea2abc9a9fd243714309a1b4e6ab))
* **components:** 修复按钮Tab自适应主题颜色 ([3d1f419](https://github.com/honghuangdc/soybean-admin/commit/3d1f41925d54ebe89f1bbbdfe916be59bb97c9cf))
* **components:** 修复BaseLayout的HorizontalLayout ([0344f46](https://github.com/honghuangdc/soybean-admin/commit/0344f46c9377acfb52c28cf373a5416845d1aa1b))
* **components:** 修复tab组件适应暗黑主题模式 ([2fe3d27](https://github.com/honghuangdc/soybean-admin/commit/2fe3d27a36b641339fd87eaa7acad8c3424b97b4))
* **components:** tab组件在黑暗模式下泛白的颜色问题以及chromeTab的重叠问题 ([6797dbf](https://github.com/honghuangdc/soybean-admin/commit/6797dbf1b0617dcca662a25cf663d93dc4ad5807))
* **deps:** 降低vite版本新版本有些许问题 ([b429c8b](https://github.com/honghuangdc/soybean-admin/commit/b429c8b8ca61191c6bed1c52742ddd5fcf9ddc3a))
* **deps:** 去除图片验证码依赖 ([76a1afa](https://github.com/honghuangdc/soybean-admin/commit/76a1afae4e87c3c08f7fd31b20323c0456565f64))
* **deps:** vite依赖放入devDependencies ([7527b1f](https://github.com/honghuangdc/soybean-admin/commit/7527b1f07cdc2d82ec0104ed7317c7ff731da0b7))
* **hooks:** 修复登录页切换登录页参数丢失问题 ([789855a](https://github.com/honghuangdc/soybean-admin/commit/789855a3786623893aa55a2f6c977155394a8a44))
* **hooks:** 修复toLogin函数导致登录重定向地址过多 ([b4adf67](https://github.com/honghuangdc/soybean-admin/commit/b4adf678a4f96f670f9cbdcaebe21378fa94c77c))
* **projects:** 布局修复:从填充屏幕高的页面切换至滚动页面导致布局坍塌 ([2fdb5f5](https://github.com/honghuangdc/soybean-admin/commit/2fdb5f563f7d9fa00d8e5343d992342ff34e3a5a))
* **projects:** 更正dashboard的布局文件 ([31fda0c](https://github.com/honghuangdc/soybean-admin/commit/31fda0ce992457972205db3a39e4c7327d21c087))
* **projects:** 关于页面:开发环境依赖更正 ([3b3baf9](https://github.com/honghuangdc/soybean-admin/commit/3b3baf93ee36423bfe4fc0ab24eda0f99ce92363))
* **projects:** 腾讯地图容器高自适应 ([d7054c5](https://github.com/honghuangdc/soybean-admin/commit/d7054c599b1ce59a123667443863a8054ba19a90))
* **projects:** 头部logo链接更正 ([5d8c3f5](https://github.com/honghuangdc/soybean-admin/commit/5d8c3f54a3e414cdeff35bf5ddb2a1e13d7d703a))
* **projects:** 完善侧边菜单展开逻辑 ([b5f0512](https://github.com/honghuangdc/soybean-admin/commit/b5f05128abcf2403181b7cc7800d9e6593844657))
* **projects:** 修复百度地图sdk地址 ([9a97d23](https://github.com/honghuangdc/soybean-admin/commit/9a97d23c755b7fa7c3166d783e99cac10a0a9753))
* **projects:** 修复登录的重定向地址 ([f97f226](https://github.com/honghuangdc/soybean-admin/commit/f97f2266566164cad912e7ffcdebee1c1b2f4324))
* **projects:** 修复登录页刷新跳404 ([358d4e8](https://github.com/honghuangdc/soybean-admin/commit/358d4e8a1992aa040b909ae580470a0fd2142f5f))
* **projects:** 修复顶部加载条主题 ([ea5917d](https://github.com/honghuangdc/soybean-admin/commit/ea5917d2258356bbcb296420ea1d017f5ad05b7a))
* **projects:** 修复多级菜单页面multitab显示问题 ([f0474bd](https://github.com/honghuangdc/soybean-admin/commit/f0474bd96104dcca332d35d8202eedc3df00eb10))
* **projects:** 修复多页签删除功能 ([99adbc5](https://github.com/honghuangdc/soybean-admin/commit/99adbc5a30c9128d005dc8096d58c5b320f67fef))
* **projects:** 修复分析页折线图表布局问题 ([43b832b](https://github.com/honghuangdc/soybean-admin/commit/43b832bee0dc1d852f3e435f16eaa37f27b0f66c))
* **projects:** 修复富文本编辑器在亮色主题下全屏后背景色丢失 ([4ab7702](https://github.com/honghuangdc/soybean-admin/commit/4ab7702186e1121e50f1d4725b73f28498aba312))
* **projects:** 修复没有子页面的路由写法问题 ([b80c224](https://github.com/honghuangdc/soybean-admin/commit/b80c2246641d44b9ad35dfbfb3d17500cfcb6e43))
* **projects:** 修复同时显示两种multiTab ([5be2e2a](https://github.com/honghuangdc/soybean-admin/commit/5be2e2a2e5658e09c47a4dc1331129e14ed6d761))
* **projects:** 修复页面滚动和页面100%视高占比 ([fa2cc78](https://github.com/honghuangdc/soybean-admin/commit/fa2cc789371999de6b2f698ba7ed87a4d740ad37))
* **projects:** 修复页面滚动行为 ([57e00e6](https://github.com/honghuangdc/soybean-admin/commit/57e00e64177bc9925ca95785335786836571766a))
* **projects:** 修复页面缓存 ([fa0a907](https://github.com/honghuangdc/soybean-admin/commit/fa0a907941a90ed72288205fef14b0923a0ffd8e))
* **projects:** 修复页面缓存,添加多页签删除 ([2489374](https://github.com/honghuangdc/soybean-admin/commit/248937479cc9ccb936116300d628dfa734014b37))
* **projects:** 修复在暗黑模式下第一次进入网页不会触发暗黑模式监听 ([c4a652e](https://github.com/honghuangdc/soybean-admin/commit/c4a652e21e4c3e2ee6e86e04e46d5dccd579d584))
* **projects:** 修复主题配置 ([ff24fda](https://github.com/honghuangdc/soybean-admin/commit/ff24fda5ee12074e7130122ca311d0ce174cc184))
* **projects:** 修复主题相关,自适应操作系统暗黑模式 ([bfa42d7](https://github.com/honghuangdc/soybean-admin/commit/bfa42d769d464dbc8d51689c5fc8c59a348941fb))
* **projects:** 修复globalFooter适应暗黑模式 ([93f08d9](https://github.com/honghuangdc/soybean-admin/commit/93f08d90671b3ddfbdb969d5b13f4a3fa9903a19))
* **projects:** 修复multiTab关闭逻辑添加关闭左边和右边的标签右键操作 ([ed90cb8](https://github.com/honghuangdc/soybean-admin/commit/ed90cb8f8e8d3bbf594757caa950f8521869ece4))
* **projects:** 修复tab过多时样式坍塌添加tab横向滚动 ([0ec4d21](https://github.com/honghuangdc/soybean-admin/commit/0ec4d218e365f54ab0c138a955dcd990cbf2d9bc))
* **projects:** 修复tab在移动端无法点击 ([1a76de0](https://github.com/honghuangdc/soybean-admin/commit/1a76de04463b0344b39c09df0e0762825d66653b))
* **projects:** 修复vertical sider自适应主题 ([9097fa3](https://github.com/honghuangdc/soybean-admin/commit/9097fa386687d077a480033d9978cfbd59e0e3a0))
* **projects:** 修复vertical-mix导航模式的二级菜单显示问题 ([6f286e6](https://github.com/honghuangdc/soybean-admin/commit/6f286e674724db12d6c5a4339ba6f3db720b781d))
* **projects:** 页面各部分背景颜色添加自然过渡 ([1c5fdca](https://github.com/honghuangdc/soybean-admin/commit/1c5fdca59637c141ae1f0b47d9bcf05788a631c2))
* **projects:** wangEditor在暗黑模式下的背景色问题 ([a7de314](https://github.com/honghuangdc/soybean-admin/commit/a7de31404508a2d4436435d06cdb63f851a86029))
* **types:** 数据类型 EnumDataType.boolean 为 [object Boolean] ([e9b5560](https://github.com/honghuangdc/soybean-admin/commit/e9b55608f960c0d3cdeca91af6f2777a23fd20dd))
* **types:** 修复naive组件回调函数参数类型错误 ([667282f](https://github.com/honghuangdc/soybean-admin/commit/667282f81a8822006242d612a08ac59571e3508e))
* **types:** 修复TS类型错误 ([45d31a0](https://github.com/honghuangdc/soybean-admin/commit/45d31a0f5625784423bea463b2373b0cd35b37f5))
* **utils:** utils函数名称更正 ([68f4d01](https://github.com/honghuangdc/soybean-admin/commit/68f4d012cc3cce1df5cb61dfa0212126ea0b202e))
### Performance Improvements
* **projects:** 添加windicss指定的扫描目录提升构建性能 ([8e6b0b2](https://github.com/honghuangdc/soybean-admin/commit/8e6b0b299d2ef50f2b85e67b7a1aa7fd2ac1bce1))
* **deps:** 降低vite版本 ([c9c5ca9](https://github.com/honghuangdc/soybean-admin/commit/c9c5ca9989eddb084f2706155473123c5dcfc334))
* **projects:** 修复redirect-not-found子路由 ([5bfb819](https://github.com/honghuangdc/soybean-admin/commit/5bfb8199b463d9ca6430577b5c493c0b78967aa9))
* **projects:** 修复vertical-mix布局、重构初始化的loading ([579e074](https://github.com/honghuangdc/soybean-admin/commit/579e07400e1b9a52934ed808a37c8579a41e8e74))
* **projects:** 修复网络请求错误空信息的提示 ([ff9216b](https://github.com/honghuangdc/soybean-admin/commit/ff9216b621aaef0a8203386fa1c3ca5477a2edea))
* **projects:** 修复面包屑数据 ([28b5d22](https://github.com/honghuangdc/soybean-admin/commit/28b5d224010a28669ad3a1919fc49f6e2dc808cd))
* **projects:** 去除Layout组件冗余代码 ([0e783bc](https://github.com/honghuangdc/soybean-admin/commit/0e783bcf7be0b3a083fe950adfb0afc72b510f97))
* **projects:** 请求相关细节修复 ([2ad1ad3](https://github.com/honghuangdc/soybean-admin/commit/2ad1ad32b8410d84902a33d825032c282ca6df86))

21
LICENSE
View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2021 Soybean
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,13 +1,13 @@
<div align="center">
<img src="https://i.loli.net/2021/11/24/x5lLfuSnEawBAgi.png"/>
<h1>Soybean Admin</h1>
<h1>Soybean Admin Thin</h1>
</div>
[![license](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)
## 简介
Soybean Admin 是一个基于 Vue3、Vite、Naive UI、TypeScript 的免费中后台模版,它使用了最新的前端技术栈,内置丰富的插件,有着极高的代码规范,开箱即用的中后台前端解决方案,也可用于学习参考
Soybean Admin Thin 是Soybean Admin的精简版
## 特性

View File

@@ -3,6 +3,6 @@ import dayjs from 'dayjs';
/** 项目构建时间 */
const PROJECT_BUILD_TIME = JSON.stringify(dayjs().format('YYYY-MM-DD HH:mm:ss'));
export default {
export const define = {
PROJECT_BUILD_TIME
};

5
build/env/index.ts vendored
View File

@@ -1,5 +0,0 @@
import dotenv from 'dotenv';
const { parsed: viteEnv } = dotenv.config(); // 加载环境
export default viteEnv!;

View File

@@ -1,5 +1,2 @@
import viteEnv from './env';
import plugins from './plugins';
import define from './define';
export { viteEnv, plugins, define };
export * from './plugins';
export * from './define';

View File

@@ -1,12 +1,17 @@
import { loadEnv } from 'vite';
import type { ConfigEnv, PluginOption } from 'vite';
import { minifyHtml, injectHtml } from 'vite-plugin-html'; // html插件(使用变量、压缩)
import viteEnv from '../env';
export default [
minifyHtml(),
injectHtml({
injectData: {
appName: viteEnv.VITE_APP_NAME,
appTitle: viteEnv.VITE_APP_TITLE
}
})
];
export default (config: ConfigEnv): PluginOption[] => {
const viteEnv = loadEnv(config.mode, `.env.${config.mode}`);
return [
minifyHtml(),
injectHtml({
injectData: {
appName: viteEnv.VITE_APP_NAME,
appTitle: viteEnv.VITE_APP_TITLE
}
})
];
};

View File

@@ -4,6 +4,7 @@ import Components from 'unplugin-vue-components/vite'; // 从指定目录自动
export default [
Components({
dts: false,
resolvers: [IconsResolver({ componentPrefix: 'icon' })]
}),
Icons({ scale: 1, defaultClass: 'inline-block' })

View File

@@ -1,10 +1,17 @@
import type { ConfigEnv, PluginOption } from 'vite';
import vue from './vue';
import html from './html';
import iconify from './iconify';
import windicss from './windicss';
import visualizer from './visualizer';
import mock from './mock';
import visualizer from './visualizer';
const plugins = [vue, ...html, ...iconify, windicss, visualizer, mock];
export function setupVitePlugins(configEnv: ConfigEnv): (PluginOption | PluginOption[])[] {
const plugins = [vue, ...html(configEnv), ...iconify, windicss, mock];
export default plugins;
if (configEnv.command === 'build') {
plugins.push(visualizer);
}
return plugins;
}

View File

@@ -7,26 +7,24 @@
<title><%= appName %></title>
</head>
<body>
<div id="appProvider" style="display: none"></div>
<div id="app">
<!-- 页面渲染之前加载动画 -->
<div class="app-loading">
<img class="app-loading_logo" src="/resource/logo.png" />
<div class="app-loading__dot-wrapper">
<div class="app-loading__dot">
<i class="left top"></i>
<i class="left bottom delay-400"></i>
<i class="right top delay-800"></i>
<i class="right bottom delay-1200"></i>
<div class="loading-container">
<div id="loadingLogo" class="loading-svg"></div>
<div class="loading-spin__container">
<div class="loading-spin">
<div class="left-0 top-0 loading-spin-item"></div>
<div class="left-0 bottom-0 loading-spin-item loading-delay-500"></div>
<div class="right-0 top-0 loading-spin-item loading-delay-1000"></div>
<div class="right-0 bottom-0 loading-spin-item loading-delay-1500"></div>
</div>
</div>
<h2 class="app-loading_title"><%= appTitle %></h2>
<style>
@import '/resource/loading.css';
</style>
<h2 class="loading-title"><%= appTitle %></h2>
</div>
<!-- End -->
</div>
<style>
@import '/resource/loading.css';
</style>
<script src="/resource/loading.js"></script>
</div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@@ -1,16 +1,93 @@
import type { MockMethod } from 'vite-plugin-mock';
import type { BackendServiceResult } from '@/interface';
export default [
const token: ApiAuth.Token = {
token: '__TEMP_TOKEN__',
refreshToken: '__TEMP_REFRESH_TOKEN__'
};
const apis: MockMethod[] = [
// 获取验证码
{
url: '/api/getUser',
method: 'get',
response: (): BackendServiceResult => {
url: '/mock/getSmsCode',
method: 'post',
response: (): Service.BackendServiceResult<boolean> => {
return {
code: 200,
message: 'ok',
data: '测试mock数据'
data: true
};
}
},
// 密码登录
{
url: '/mock/loginByPwd',
method: 'post',
response: (): Service.BackendServiceResult<ApiAuth.Token> => {
return {
code: 200,
message: 'ok',
data: token
};
}
},
// 验证码登录
{
url: '/mock/loginByCode',
method: 'post',
response: (): Service.BackendServiceResult<ApiAuth.Token> => {
return {
code: 200,
message: 'ok',
data: token
};
}
},
// 获取用户信息(请求头携带token)
{
url: '/mock/getUserInfo',
method: 'get',
response: (): Service.BackendServiceResult<ApiAuth.UserInfo> => {
return {
code: 200,
message: 'ok',
data: {
userId: '0',
userName: 'Soybean',
userPhone: '15170283876',
userRole: 'super'
}
};
}
},
{
url: '/mock/testToken',
method: 'post',
response: (option: any): Service.BackendServiceResult<true | null> => {
if (option.headers?.authorization !== token.token) {
return {
code: 66666,
message: 'token 失效',
data: null
};
}
return {
code: 200,
message: 'ok',
data: true
};
}
},
{
url: '/mock/updateToken',
method: 'post',
response: (): Service.BackendServiceResult<string> => {
return {
code: 200,
message: 'ok',
data: token.token
};
}
}
] as MockMethod[];
];
export default apis;

View File

@@ -1,3 +1,4 @@
import auth from './auth';
import route from './route';
export default [...auth];
export default [...auth, ...route];

211
mock/api/route.ts Normal file
View File

@@ -0,0 +1,211 @@
import type { MockMethod } from 'vite-plugin-mock';
const routes: AuthRoute.Route[] = [
{
name: 'dashboard',
path: '/dashboard',
component: 'basic',
children: [
{
name: 'dashboard_analysis',
path: '/dashboard/analysis',
component: 'self',
meta: {
title: '分析页'
}
},
{
name: 'dashboard_workbench',
path: '/dashboard/workbench',
component: 'self',
meta: {
title: '工作台',
permissions: ['super', 'admin']
}
}
],
meta: {
title: '仪表盘',
requiresAuth: true,
icon: 'carbon:dashboard',
order: 1
}
},
{
name: 'document',
path: '/document',
component: 'basic',
children: [
{
name: 'document_vue',
path: '/document/vue',
component: 'self',
meta: {
title: 'vue文档'
}
},
{
name: 'document_vue-new',
path: '/document/vue-new',
component: 'self',
meta: {
title: 'vue文档(新版)'
}
},
{
name: 'document_vite',
path: '/document/vite',
component: 'self',
meta: {
title: 'vite文档'
}
},
{
name: 'document_naive',
path: '/document/naive',
component: 'self',
meta: {
title: 'naive文档'
}
},
{
name: 'document_project',
path: '/document/project',
meta: {
title: '项目文档(外链)',
href: 'https://docs.soybean.pro/'
}
}
],
meta: {
title: '文档',
icon: 'carbon:document',
order: 2
}
},
{
name: 'about',
path: '/about',
component: 'self',
meta: {
title: '关于',
singleLayout: 'basic',
permissions: ['super', 'admin', 'test'],
icon: 'fluent:book-information-24-regular',
order: 7
}
},
{
name: 'exception',
path: '/exception',
component: 'basic',
children: [
{
name: 'exception_403',
path: '/exception/403',
component: 'self',
meta: {
title: '异常页403'
}
},
{
name: 'exception_404',
path: '/exception/404',
component: 'self',
meta: {
title: '异常页404'
}
},
{
name: 'exception_500',
path: '/exception/500',
component: 'self',
meta: {
title: '异常页500'
}
}
],
meta: {
title: '异常页',
icon: 'ant-design:exception-outlined',
order: 5
}
},
{
name: 'multi-menu',
path: '/multi-menu',
component: 'basic',
children: [
{
name: 'multi-menu_first',
path: '/multi-menu/first',
component: 'multi',
children: [
{
name: 'multi-menu_first_second',
path: '/multi-menu/first/second',
component: 'self',
meta: {
title: '二级菜单'
}
},
{
name: 'multi-menu_first_second-new',
path: '/multi-menu/first/second-new',
component: 'multi',
children: [
{
name: 'multi-menu_first_second-new_third',
path: '/multi-menu/first/second-new/third',
component: 'self',
meta: {
title: '三级菜单'
}
}
],
meta: {
title: '二级菜单(有子菜单)'
}
}
],
meta: {
title: '一级菜单'
}
}
],
meta: {
title: '多级菜单',
icon: 'carbon:menu',
order: 6
}
}
];
function dataMiddleware(data: AuthRoute.Route[]): ApiRoute.Route {
const routeHomeName: AuthRoute.RouteKey = 'dashboard_analysis';
function sortRoutes(sorts: AuthRoute.Route[]) {
return sorts.sort((next, pre) => Number(next.meta?.order) - Number(pre.meta?.order));
}
return {
routes: sortRoutes(data),
home: routeHomeName
};
}
const apis: MockMethod[] = [
{
url: '/mock/getUserRoutes',
method: 'post',
response: (): Service.BackendServiceResult => {
return {
code: 200,
message: 'ok',
data: dataMiddleware(routes)
};
}
}
];
export default apis;

View File

@@ -1,24 +1,17 @@
{
"name": "soybean-admin",
"version": "0.0.5",
"author": {
"name": "Soybean",
"email": "honghuangdc@gmail.com",
"url": "https://github.com/honghuangdc"
},
"name": "soybean-admin-thin",
"version": "0.1.1",
"scripts": {
"dev": "cross-env VITE_HTTP_ENV=test vite",
"dev:prod": "cross-env VITE_HTTP_ENV=prod vite",
"typecheck": "vue-tsc",
"build": "npm run typecheck && cross-env VITE_HTTP_ENV=prod vite build",
"build:test": "npm run typecheck && cross-env VITE_HTTP_ENV=test vite build",
"build:vercel": "npm run typecheck && cross-env VITE_HTTP_ENV=prod VITE_IS_VERCEL=1 vite build",
"preview": "vite preview --port 5050",
"release": "standard-version",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md",
"dev": "vite",
"dev:prod": "vite --mode production",
"dev:staging": "vite --mode staging",
"vtsc": "vue-tsc --noEmit --skipLibCheck",
"build": "npm run vtsc && vite build",
"build:dev": "npm run vtsc && vite build --mode development",
"build:staging": "npm run vtsc && vite build --mode staging",
"serve": "vite preview",
"lint": "eslint ./src --ext .vue,.js,jsx,.ts,tsx",
"lint:fix": "eslint --fix ./src --ext .vue,.js,jsx,.ts,tsx",
"lint": "eslint --fix ./ --ext .vue,.js,jsx,.ts,tsx",
"prepare": "husky install",
"postinstall": "patch-package"
},
@@ -31,77 +24,62 @@
}
},
"dependencies": {
"@antv/g2plot": "^2.4.5",
"@antv/g2plot": "^2.4.7",
"@better-scroll/core": "^2.4.2",
"@vueuse/core": "^7.5.1",
"axios": "^0.24.0",
"chroma-js": "^2.1.2",
"@vueuse/core": "^7.5.3",
"axios": "^0.25.0",
"clipboard": "^2.0.8",
"colord": "^2.9.2",
"crypto-js": "^4.1.1",
"dayjs": "^1.10.7",
"form-data": "^4.0.0",
"naive-ui": "^2.23.2",
"lodash-es": "^4.17.21",
"naive-ui": "^2.24.1",
"pinia": "^2.0.9",
"print-js": "^1.6.0",
"qs": "^6.10.2",
"swiper": "^7.4.1",
"vditor": "^3.8.10",
"qs": "^6.10.3",
"vue": "^3.2.26",
"vue-router": "^4.0.12",
"wangeditor": "^4.7.11",
"xgplayer": "^2.31.4"
"vue-router": "^4.0.12"
},
"devDependencies": {
"@amap/amap-jsapi-types": "^0.0.8",
"@commitlint/cli": "^16.0.1",
"@commitlint/cli": "^16.0.3",
"@commitlint/config-conventional": "^16.0.0",
"@iconify/json": "^1.1.450",
"@iconify/vue": "^3.1.1",
"@types/bmapgl": "^0.0.5",
"@types/chroma-js": "^2.1.3",
"@iconify/json": "^1.1.458",
"@iconify/vue": "^3.1.2",
"@types/crypto-js": "^4.1.0",
"@types/node": "^17.0.10",
"@types/qs": "^6.9.7",
"@typescript-eslint/eslint-plugin": "^5.8.1",
"@typescript-eslint/parser": "^5.8.1",
"@typescript-eslint/eslint-plugin": "^5.10.0",
"@typescript-eslint/parser": "^5.10.0",
"@vitejs/plugin-vue": "^2.0.1",
"@vue/compiler-sfc": "^3.2.26",
"@vue/eslint-config-prettier": "^7.0.0",
"@vue/eslint-config-typescript": "^10.0.0",
"commitizen": "^4.2.4",
"cross-env": "^7.0.3",
"cz-conventional-changelog": "^3.3.0",
"cz-customizable": "^6.3.0",
"dotenv": "^10.0.0",
"eslint": "^8.6.0",
"eslint": "^8.7.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-import": "^2.25.3",
"eslint-plugin-import": "^2.25.4",
"eslint-plugin-prettier": "^4.0.0",
"eslint-plugin-vue": "^8.2.0",
"eslint-plugin-vue": "^8.3.0",
"husky": "^7.0.4",
"lint-staged": "^12.1.4",
"lint-staged": "^12.2.1",
"mockjs": "^1.1.0",
"patch-package": "^6.4.7",
"postinstall-postinstall": "^2.1.0",
"prettier": "^2.5.1",
"rollup-plugin-visualizer": "^5.5.2",
"sass": "^1.45.2",
"rollup-plugin-visualizer": "^5.5.4",
"sass": "^1.49.0",
"typescript": "^4.5.4",
"unplugin-icons": "^0.13.0",
"unplugin-vue-components": "^0.17.11",
"vite": "~2.5.10",
"unplugin-vue-components": "^0.17.13",
"vite": "^2.7.13",
"vite-plugin-html": "^2.1.2",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-windicss": "^1.6.1",
"vue-tsc": "^0.30.1",
"vueuc": "^0.4.19",
"windicss": "^3.4.2"
},
"homepage": "https://github.com/honghuangdc/soybean-admin",
"repository": {
"type": "git",
"url": "git+https://github.com/honghuangdc/soybean-admin.git"
},
"bugs": {
"url": "https://github.com/honghuangdc/soybean-admin/issues"
"vite-plugin-windicss": "^1.6.3",
"vue-tsc": "^0.30.6",
"vueuc": "^0.4.23",
"windicss": "^3.4.3"
}
}

2890
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,91 +1,91 @@
.app-loading {
position: fixed;
left: 0;
top: 0;
z-index: -1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background-color:#f5f7f9;
.loading-container {
position: fixed;
left: 0;
top: 0;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
}
.app-loading_logo {
width: 128px;
height: 128px;
.loading-svg {
width: 128px;
height: 128px;
color: var(--primary-color);
}
.app-loading__dot-wrapper {
width: 56px;
height: 56px;
margin: 36px 0;
.loading-spin__container {
width: 56px;
height: 56px;
margin: 36px 0;
}
.app-loading__dot {
position: relative;
height: 100%;
transform: rotate(45deg);
animation-name: loadingRotate;
animation-duration: 1.2s;
animation-iteration-count: infinite;
animation-timing-function: linear;
.loading-spin {
position: relative;
height: 100%;
animation: loadingSpin 1s linear infinite;
}
@keyframes loadingRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
@-webkit-keyframes loadingRotate {
to {
-webkit-transform: rotate(405deg);
transform: rotate(405deg);
}
}
.app-loading__dot > i {
position: absolute;
display: block;
width: 18px;
height: 18px;
background: #1890ff;
border-radius: 50%;
-webkit-transform: scale(0.75);
transform: scale(0.75);
transform-origin: 50% 50%;
opacity: 0.3;
animation: spinOpacity 1s infinite linear alternate;
}
@keyframes spinOpacity {
to {
opacity: 1;
}
}
@-webkit-keyframes spinOpacity {
to {
opacity: 1;
}
}
.delay-400 {
animation-delay: 0.4s !important;
}
.delay-800 {
animation-delay: 0.8s !important;
}
.delay-1200 {
animation-delay: 1.2s !important;
}
.left {
.left-0 {
left: 0;
}
.right {
.right-0 {
right: 0;
}
.top {
.top-0 {
top: 0;
}
.bottom {
.bottom-0 {
bottom: 0;
}
.app-loading_title {
.loading-spin-item {
position: absolute;
height: 16px;
width: 16px;
background-color: var(--primary-color);
border-radius: 8px;
-webkit-animation: loadingPulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
animation: loadingPulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@keyframes loadingSpin {
from {
-webkit-transform: rotate(0deg);
transform: rotate(0deg);
}
to {
-webkit-transform: rotate(360deg);
transform: rotate(360deg);
}
}
@keyframes loadingPulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: .5;
}
}
.loading-delay-500 {
-webkit-animation-delay: 500ms;
animation-delay: 500ms;
}
.loading-delay-1000 {
-webkit-animation-delay: 1000ms;
animation-delay: 1000ms;
}
.loading-delay-1500 {
-webkit-animation-delay: 1500ms;
animation-delay: 1500ms;
}
.loading-title {
font-size: 28px;
font-weight: 500;
color: #646464;
}

View File

@@ -0,0 +1,44 @@
/**
* 初始化加载效果的svg格式logo
* @param { string }id - 元素id
*/
function initSvgLogo(id) {
const svgStr = `<svg width="128px" height="128px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
y="0px" viewBox="0 0 158.9 158.9" style="enable-background:new 0 0 158.9 158.9;" xml:space="preserve">
<path style="fill:none" d="M0,158.9C0,106.3,0,53.7,0,1.1C0,0.2,0.2,0,1.1,0c52.2,0,104.5,0,156.7,0c0.9,0,1.1,0.2,1.1,1.1
c0,52.2,0,104.5,0,156.7c0,0.9-0.2,1.1-1.1,1.1C105.2,158.8,52.6,158.8,0,158.9z" />
<path style="fill:currentColor" d="M81.3,55.9c-0.1-11.7-2.9-22.5-9.4-32.4c-1-1.5-2.1-2.9-2.5-4.7c-0.7-3.4,0.9-6.9,4-8.6c3-1.7,6.8-1.2,9.3,1.2
c2.4,2.6,4.4,5.6,5.9,8.8c4.7,8.9,7.6,18.6,8.4,28.6c1,12.5-0.7,25-5.2,36.7c-0.9,2.5-1.9,4.9-3,7.3c-0.3,0.4-0.3,1,0,1.4
c9.6,13.3,21.8,23,37.8,27.2c6.4,1.7,13.1,2.3,19.7,1.6c4.2-0.4,7.9,2.7,8.4,6.9c0.7,4.3-2.3,8.3-6.6,9c0,0,0,0-0.1,0
c-7.7,0.9-15.5,0.5-23-1.3c-13.9-3.1-26.7-10-36.9-19.9c-4.4-4.2-8.4-8.8-11.9-13.7c-0.5-0.8-1.4-1.2-2.3-1.1
c-9.5,0.7-18.8,3.3-27.4,7.6c-11.6,6-20.7,14.6-26.4,26.4c-0.7,1.9-2,3.5-3.7,4.7c-2.9,1.7-6.6,1.5-9.2-0.7c-2.8-2.2-3.8-6-2.4-9.3
c2.2-5.2,5.1-10.1,8.7-14.5c12.2-15.4,28.2-24.6,47.3-28.6c4-0.8,8.1-1.4,12.2-1.6c0.5,0,1-0.3,1.2-0.8c3.3-7.1,5.5-14.6,6.5-22.3
C81.1,61.2,81.3,58.6,81.3,55.9z" />
<path style="fill:currentColor" d="M136.3,108.3c-3.8-0.5-7.6-1.4-11.1-2.9c-7.7-2.8-14.4-7.5-19.7-13.8c-2.9-3.3-2.5-8.4,0.8-11.3
c1.4-1.2,3.1-1.9,4.9-1.9c2.5-0.1,5,1,6.5,2.9c4.9,5.6,11.6,9.4,18.9,10.8c1.5,0.2,3.1,0.6,4.5,1.2c3.2,1.8,4.8,5.6,3.8,9.2
C144,106.1,140.8,108.4,136.3,108.3z" />
<path style="fill:currentColor" d="M55.7,33.3c3,0.2,5.6,2.2,6.6,5c2.2,5.4,3.4,11.2,3.6,17c0.3,5.9-0.6,11.7-2.5,17.3c-2,5.8-8.2,7.8-12.9,4.2
c-2.6-2.2-3.6-5.8-2.4-9c1.4-4,1.9-8.2,1.7-12.4c-0.2-3.8-1-7.5-2.4-11C45.3,38.9,49.2,33.3,55.7,33.3z" />
<path style="fill:currentColor" d="M77.9,126.6c0,3.9-2.8,7.2-6.7,7.9c-7.8,1.5-14.8,5.9-19.7,12.2c-2.7,3.5-7.6,4.2-11.2,1.6
c-3.6-2.6-4.3-7.6-1.7-11.2c0.1-0.1,0.2-0.3,0.3-0.4c4.1-5.2,9.3-9.6,15.1-12.8c4.4-2.5,9.1-4.2,14-5.1
C73.3,117.7,77.9,121.3,77.9,126.6z" />
</svg>
`;
const appEl = document.querySelector(id);
const div = document.createElement('div');
div.innerHTML = svgStr;
if (appEl) {
appEl.appendChild(div);
}
}
function addThemeColorCssVars() {
const key = '__THEME_COLOR__';
const themeColor = '#1890ff';
const cssVars = window.localStorage.getItem(key) || `--primary-color: ${themeColor}`;
document.documentElement.style.cssText = cssVars;
}
initSvgLogo('#loadingLogo');
addThemeColorCssVars();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -1,13 +1,24 @@
<template>
<app-provider>
<router-view />
</app-provider>
<n-config-provider
:theme="theme.naiveTheme"
:theme-overrides="theme.naiveThemeOverrides"
:locale="zhCN"
:date-locale="dateZhCN"
class="h-full"
>
<naive-provider>
<router-view />
</naive-provider>
</n-config-provider>
</template>
<script lang="ts" setup>
import { fetchTestMock } from '@/service';
import AppProvider from './AppProvider.vue';
<script setup lang="ts">
import { NConfigProvider, zhCN, dateZhCN } from 'naive-ui';
import { NaiveProvider } from '@/components';
import { useThemeStore, subscribeStore } from '@/store';
fetchTestMock();
const theme = useThemeStore();
subscribeStore();
</script>
<style></style>
<style scoped></style>

View File

@@ -1,33 +0,0 @@
<template>
<n-config-provider
class="h-full"
:locale="zhCN"
:date-locale="dateZhCN"
:theme="naiveTheme"
:theme-overrides="theme.themeOverrids"
>
<n-element class="h-full">
<naive-provider>
<slot></slot>
</naive-provider>
</n-element>
</n-config-provider>
</template>
<script lang="ts" setup>
import { NConfigProvider, NElement, zhCN, dateZhCN } from 'naive-ui';
import { NaiveProvider } from '@/components';
import { useThemeStore } from '@/store';
import { useDarkMode, useGlobalEvent } from '@/composables';
const theme = useThemeStore();
const { naiveTheme } = useDarkMode();
const { initGlobalEventListener } = useGlobalEvent();
function init() {
initGlobalEventListener();
}
init();
</script>
<style></style>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 25.4.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 158.9 158.9" style="enable-background:new 0 0 158.9 158.9;" xml:space="preserve">
<style type="text/css">
.st0{fill:none;}
.st1{fill:#409EFF;}
</style>
<path class="st0" d="M0,158.9C0,106.3,0,53.7,0,1.1C0,0.2,0.2,0,1.1,0c52.2,0,104.5,0,156.7,0c0.9,0,1.1,0.2,1.1,1.1
c0,52.2,0,104.5,0,156.7c0,0.9-0.2,1.1-1.1,1.1C105.2,158.8,52.6,158.8,0,158.9z"/>
<path class="st1" d="M81.3,55.9c-0.1-11.7-2.9-22.5-9.4-32.4c-1-1.5-2.1-2.9-2.5-4.7c-0.7-3.4,0.9-6.9,4-8.6c3-1.7,6.8-1.2,9.3,1.2
c2.4,2.6,4.4,5.6,5.9,8.8c4.7,8.9,7.6,18.6,8.4,28.6c1,12.5-0.7,25-5.2,36.7c-0.9,2.5-1.9,4.9-3,7.3c-0.3,0.4-0.3,1,0,1.4
c9.6,13.3,21.8,23,37.8,27.2c6.4,1.7,13.1,2.3,19.7,1.6c4.2-0.4,7.9,2.7,8.4,6.9c0.7,4.3-2.3,8.3-6.6,9c0,0,0,0-0.1,0
c-7.7,0.9-15.5,0.5-23-1.3c-13.9-3.1-26.7-10-36.9-19.9c-4.4-4.2-8.4-8.8-11.9-13.7c-0.5-0.8-1.4-1.2-2.3-1.1
c-9.5,0.7-18.8,3.3-27.4,7.6c-11.6,6-20.7,14.6-26.4,26.4c-0.7,1.9-2,3.5-3.7,4.7c-2.9,1.7-6.6,1.5-9.2-0.7c-2.8-2.2-3.8-6-2.4-9.3
c2.2-5.2,5.1-10.1,8.7-14.5c12.2-15.4,28.2-24.6,47.3-28.6c4-0.8,8.1-1.4,12.2-1.6c0.5,0,1-0.3,1.2-0.8c3.3-7.1,5.5-14.6,6.5-22.3
C81.1,61.2,81.3,58.6,81.3,55.9z"/>
<path class="st1" d="M136.3,108.3c-3.8-0.5-7.6-1.4-11.1-2.9c-7.7-2.8-14.4-7.5-19.7-13.8c-2.9-3.3-2.5-8.4,0.8-11.3
c1.4-1.2,3.1-1.9,4.9-1.9c2.5-0.1,5,1,6.5,2.9c4.9,5.6,11.6,9.4,18.9,10.8c1.5,0.2,3.1,0.6,4.5,1.2c3.2,1.8,4.8,5.6,3.8,9.2
C144,106.1,140.8,108.4,136.3,108.3z"/>
<path class="st1" d="M55.7,33.3c3,0.2,5.6,2.2,6.6,5c2.2,5.4,3.4,11.2,3.6,17c0.3,5.9-0.6,11.7-2.5,17.3c-2,5.8-8.2,7.8-12.9,4.2
c-2.6-2.2-3.6-5.8-2.4-9c1.4-4,1.9-8.2,1.7-12.4c-0.2-3.8-1-7.5-2.4-11C45.3,38.9,49.2,33.3,55.7,33.3z"/>
<path class="st1" d="M77.9,126.6c0,3.9-2.8,7.2-6.7,7.9c-7.8,1.5-14.8,5.9-19.7,12.2c-2.7,3.5-7.6,4.2-11.2,1.6
c-3.6-2.6-4.3-7.6-1.7-11.2c0.1-0.1,0.2-0.3,0.3-0.4c4.1-5.2,9.3-9.6,15.1-12.8c4.4-2.5,9.1-4.2,14-5.1
C73.3,117.7,77.9,121.3,77.9,126.6z"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1,48 @@
<template>
<div class="w-full text-14px">
<n-checkbox v-model:checked="checked">我已经仔细阅读并接受</n-checkbox>
<n-button :text="true" type="primary" @click="handleClickProtocol">用户协议</n-button>
<n-button :text="true" type="primary" @click="handleClickPolicy">隐私权政策</n-button>
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { NCheckbox, NButton } from 'naive-ui';
interface Props {
/** 是否勾选 */
value?: boolean;
}
interface Emits {
(e: 'update:value', value: boolean): void;
/** 点击协议 */
(e: 'click-protocol'): void;
/** 点击隐私政策 */
(e: 'click-policy'): void;
}
const props = withDefaults(defineProps<Props>(), {
value: true
});
const emit = defineEmits<Emits>();
const checked = computed({
get() {
return props.value;
},
set(newValue: boolean) {
emit('update:value', newValue);
}
});
function handleClickProtocol() {
emit('click-protocol');
}
function handleClickPolicy() {
emit('click-policy');
}
</script>
<style scoped></style>

View File

@@ -0,0 +1,3 @@
import LoginAgreement from './LoginAgreement/index.vue';
export { LoginAgreement };

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +0,0 @@
import Banner1 from './Banner1.vue';
import Banner2 from './Banner2.vue';
import Banner3 from './Banner3.vue';
export { Banner1, Banner2, Banner3 };

View File

@@ -1,24 +0,0 @@
<template>
<div :style="{ color }">
<banner1 v-if="type === '1'" />
<banner2 v-if="type === '2'" />
<banner3 v-if="type === '3'" />
</div>
</template>
<script lang="ts" setup>
import { Banner1, Banner2, Banner3 } from './components';
interface Props {
/** banner类型 */
type?: '1' | '2' | '3';
/** 主题颜色 */
color?: string;
}
withDefaults(defineProps<Props>(), {
type: '1',
color: '#409eff'
});
</script>
<style scoped></style>

View File

@@ -0,0 +1,10 @@
<template>
<div
class="bg-white text-[#333639] dark:(bg-[#18181c] text-white text-opacity-82) transition-all duration-300 ease-in-out"
>
<slot></slot>
</div>
</template>
<script setup lang="ts"></script>
<style scoped></style>

View File

@@ -0,0 +1,39 @@
<template>
<div class="flex-center text-18px hover:text-primary cursor-pointer" @click="handleSwitch">
<icon-mdi-moon-waning-crescent v-if="darkMode" />
<icon-mdi-white-balance-sunny v-else />
</div>
</template>
<script setup lang="ts">
import { computed } from 'vue';
interface Props {
/** 暗黑模式 */
dark?: boolean;
}
interface Emits {
(e: 'update:dark', darkMode: boolean): void;
}
const props = withDefaults(defineProps<Props>(), {
dark: false
});
const emit = defineEmits<Emits>();
const darkMode = computed({
get() {
return props.dark;
},
set(newValue: boolean) {
emit('update:dark', newValue);
}
});
function handleSwitch() {
darkMode.value = !darkMode.value;
}
</script>
<style scoped></style>

View File

@@ -1,75 +0,0 @@
<template>
<n-popover placement="bottom-end" trigger="click">
<template #trigger>
<n-input v-model:value="modelValue" readonly placeholder="点击选择图标">
<template #suffix>
<Icon :icon="modelValue ? modelValue : emptyIcon" class="text-30px p-5px" />
</template>
</n-input>
</template>
<template #header>
<n-input v-model:value="searchValue" placeholder="搜索图标"></n-input>
</template>
<div v-if="iconsList.length > 0" class="grid grid-cols-9 h-auto overflow-auto">
<template v-for="iconItem in iconsList" :key="iconItem">
<Icon
:icon="iconItem"
class="border-1px border-[#d9d9d9] text-30px m-2px p-5px"
:style="{ 'border-color': modelValue === iconItem ? theme.themeColor : '' }"
@click="handleChange(iconItem)"
/>
</template>
</div>
<n-empty v-else class="w-306px" description="你什么也找不到" />
</n-popover>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
import { NPopover, NInput, NEmpty } from 'naive-ui';
import { Icon } from '@iconify/vue';
import { useThemeStore } from '@/store';
interface Props {
/** 选中的图标 */
value: string;
/** 图标列表 */
icons: string[];
/** 未选中图标 */
emptyIcon?: string;
}
interface Emits {
(e: 'update:value', val: string): void;
}
const props = withDefaults(defineProps<Props>(), {
emptyIcon: 'mdi:apps'
});
const emit = defineEmits<Emits>();
const theme = useThemeStore();
const searchValue = ref('');
const iconsList = computed(() => props.icons.filter(v => v.includes(searchValue.value)));
const modelValue = computed({
get() {
return props.value;
},
set(val: string) {
emit('update:value', val);
}
});
function handleChange(iconItem: string) {
modelValue.value = iconItem;
}
</script>
<style lang="scss" scoped>
:deep(.n-input-wrapper) {
padding-right: 0;
}
:deep(.n-input__suffix) {
border: 1px solid #d9d9d9;
}
</style>

View File

@@ -1,93 +0,0 @@
<template>
<div v-if="reloadFlag" class="relative">
<slot></slot>
<div v-show="showPlaceholder" class="absolute-lt w-full h-full" :class="placeholderClass">
<div v-show="loading" class="absolute-center">
<n-spin :show="true" :size="loadingSize" />
</div>
<div v-show="isEmpty" class="absolute-center">
<div class="relative" :class="emptyNetworkClass">
<svg-empty-data class="text-primary" />
<p class="absolute-lb w-full text-center">{{ emptyDesc }}</p>
</div>
</div>
<div v-show="!network" class="absolute-center">
<div
class="relative"
:class="[{ 'cursor-pointer': showNetworkReload }, emptyNetworkClass]"
@click="handleReload"
>
<svg-network-error class="text-primary" />
<p class="absolute-lb w-full text-center">{{ networkErrorDesc }}</p>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, watch, nextTick } from 'vue';
import { NSpin } from 'naive-ui';
import { NETWORK_ERROR_MSG } from '@/config';
import { useBoolean } from '@/hooks';
import { SvgEmptyData, SvgNetworkError } from '../../svg';
interface Props {
/** 是否加载 */
loading: boolean;
/** 是否为空 */
empty?: boolean;
/** 加载图标的大小 */
loadingSize?: 'small' | 'medium' | 'large';
/** 中间占位符的class */
placeholderClass?: string;
/** 空数据描述文本 */
emptyDesc?: string;
/** 空数据和网络异常占位class */
emptyNetworkClass?: string;
/** 显示网络异常的重试点击按钮 */
showNetworkReload?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
loading: false,
empty: false,
loadingSize: 'medium',
placeholderClass: 'bg-white',
emptyDesc: '暂无数据',
emptyNetworkClass: 'w-320px h-320px text-16px text-[#666]',
showNetworkReload: false
});
// 网络状态
const { bool: network, setBool: setNetwork } = useBoolean(window.navigator.onLine);
const { bool: reloadFlag, setBool: setReload } = useBoolean(true);
// 数据是否为空
const isEmpty = computed(() => props.empty && !props.loading && network.value);
const showPlaceholder = computed(() => props.loading || isEmpty.value || !network.value);
const networkErrorDesc = computed(() =>
props.showNetworkReload ? `${NETWORK_ERROR_MSG}, 点击重试` : NETWORK_ERROR_MSG
);
function handleReload() {
if (!props.showNetworkReload) return;
setReload(false);
nextTick(() => {
setReload(true);
});
}
watch(
() => props.loading,
newValue => {
// 结束加载判断一下网络状态
if (!newValue) {
setNetwork(window.navigator.onLine);
}
}
);
</script>
<style scoped></style>

View File

@@ -1,31 +0,0 @@
<template>
<div class="absolute-lt wh-full overflow-hidden">
<div class="absolute -right-300px -top-900px">
<corner-top :start-color="themeColor" :end-color="stopColor" />
</div>
<div class="absolute -left-200px -bottom-400px">
<corner-bottom :start-color="themeColor" :end-color="stopColor" />
</div>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { mixColor } from '@/utils';
import { CornerTop, CornerBottom } from './components';
interface Props {
/** 主题颜色 */
themeColor?: string;
}
const props = withDefaults(defineProps<Props>(), {
themeColor: '#409EFF'
});
const COLOR_WHITE = '#ffffff';
const stopColor = computed(() => {
return mixColor(COLOR_WHITE, props.themeColor, 0.7);
});
</script>
<style scoped></style>

View File

@@ -1,5 +1,5 @@
<template>
<div :style="{ color }">
<div>
<svg-fill-logo v-if="fill" />
<svg-logo v-else />
</div>
@@ -11,13 +11,10 @@ import { SvgLogo, SvgFillLogo } from './components';
interface Props {
/** logo是否填充 */
fill?: boolean;
/** logo的主题颜色 */
color?: string;
}
withDefaults(defineProps<Props>(), {
fill: false,
color: '#409EFF'
fill: false
});
</script>
<style scoped></style>

View File

@@ -1,9 +1,7 @@
import NaiveProvider from './NaiveProvider/index.vue';
import SystemLogo from './SystemLogo/index.vue';
import LoginBg from './LoginBg/index.vue';
import BannerSvg from './BannerSvg/index.vue';
import DarkModeSwitch from './DarkModeSwitch/index.vue';
import DarkModeContainer from './DarkModeContainer/index.vue';
import HoverContainer from './HoverContainer/index.vue';
import LoadingEmptyWrapper from './LoadingEmptyWrapper/index.vue';
import IconSelect from './IconSelect/index.vue';
export { NaiveProvider, SystemLogo, LoginBg, BannerSvg, HoverContainer, LoadingEmptyWrapper, IconSelect };
export { NaiveProvider, SystemLogo, DarkModeSwitch, DarkModeContainer, HoverContainer };

View File

@@ -1,6 +1,6 @@
<template>
<div ref="scrollbar" class="h-full text-left">
<div ref="scrollbarContent" class="inline-block" :class="{ 'h-full': !isScrollY }">
<div ref="bsWrap" class="h-full text-left">
<div ref="bsContent" class="inline-block" :class="{ 'h-full': !isScrollY }">
<slot></slot>
</div>
</div>
@@ -8,9 +8,9 @@
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue';
import { useElementSize } from '@vueuse/core';
import BScroll from '@better-scroll/core';
import type { Options } from '@better-scroll/core';
import { useElementSize } from '@vueuse/core';
interface Props {
/** better-scroll的配置: https://better-scroll.github.io/docs/zh-CN/guide/base-scroll-options.html */
@@ -19,20 +19,21 @@ interface Props {
const props = defineProps<Props>();
const scrollbar = ref<HTMLElement | null>(null);
const bsInstance = ref<BScroll | null>(null);
const scrollbarContent = ref<HTMLElement | null>(null);
const bsWrap = ref<HTMLElement>();
const instance = ref<BScroll>();
const bsContent = ref<HTMLElement>();
const isScrollY = computed(() => Boolean(props.options.scrollY));
function initBetterScroll() {
bsInstance.value = new BScroll(scrollbar.value!, props.options);
if (!bsWrap.value) return;
instance.value = new BScroll(bsWrap.value, props.options);
}
// 滚动元素发生变化刷新BS
const { width, height } = useElementSize(scrollbarContent);
const { width, height } = useElementSize(bsContent);
watch([() => width.value, () => height.value], () => {
if (bsInstance.value) {
bsInstance.value.refresh();
if (instance.value) {
instance.value.refresh();
}
});
@@ -40,6 +41,6 @@ onMounted(() => {
initBetterScroll();
});
defineExpose({ bsInstance });
defineExpose({ instance });
</script>
<style scoped></style>

View File

@@ -1,6 +1,6 @@
<template>
<div
class="relative flex-center h-30px pl-14px border-1px border-[#e5e7eb] dark:border-[#ffffff3d] rounded-2px transition-border-color duration-300 ease-in-out cursor-pointer"
class="relative flex-center h-30px pl-14px border-1px border-[#e5e7eb] dark:border-[#ffffff3d] rounded-2px cursor-pointer transition-colors duration-300 ease-in-out"
:class="[closable ? 'pr-6px' : 'pr-14px']"
:style="buttonStyle"
@mouseenter="setTrue"
@@ -10,7 +10,7 @@
<slot></slot>
</span>
<div v-if="closable" class="pl-10px">
<icon-close :is-primary="isActive || isHover" :primary-color="primaryColor" @click="handleClose" />
<icon-close :is-active="isIconActive" :primary-color="primaryColor" @click="handleClose" />
</div>
</div>
</template>
@@ -18,8 +18,8 @@
<script lang="ts" setup>
import { computed } from 'vue';
import { useBoolean } from '@/hooks';
import { IconClose } from '@/components';
import { addColorAlpha } from '@/utils';
import IconClose from '../IconClose/index.vue';
interface Props {
/** 激活状态 */
@@ -32,27 +32,27 @@ interface Props {
darkMode?: boolean;
}
interface Emits {
/** 点击关闭图标 */
(e: 'close'): void;
}
const props = withDefaults(defineProps<Props>(), {
isActive: false,
primaryColor: '#409EFF',
primaryColor: '#1890ff',
closable: true,
darkMode: false
});
const emit = defineEmits<{
/** 点击关闭图标 */
(e: 'close'): void;
}>();
const emit = defineEmits<Emits>();
const { bool: isHover, setTrue, setFalse } = useBoolean();
function handleClose(e: MouseEvent) {
e.stopPropagation();
emit('close');
}
const isIconActive = computed(() => props.isActive || isHover.value);
const buttonStyle = computed(() => {
const style: { [key: string]: string } = {};
if (props.isActive || isHover.value) {
if (isIconActive.value) {
style.color = props.primaryColor;
style.borderColor = addColorAlpha(props.primaryColor, 0.3);
if (props.isActive) {
@@ -62,5 +62,10 @@ const buttonStyle = computed(() => {
}
return style;
});
function handleClose(e: MouseEvent) {
e.stopPropagation();
emit('close');
}
</script>
<style scoped></style>

View File

@@ -1,7 +1,7 @@
<template>
<div
class="relative flex-y-center h-34px px-24px cursor-pointer"
:class="{ '-mr-18px': !isLast, 'z-10': isActive, 'z-9': isHover }"
class="relative flex-y-center h-34px px-24px -mr-18px cursor-pointer"
:class="{ 'z-10': isActive, 'z-9': isHover }"
@mouseenter="setTrue"
@mouseleave="setFalse"
>
@@ -18,7 +18,7 @@
<slot></slot>
</span>
<div v-if="closable" class="pl-18px">
<icon-close :is-primary="isActive" :primary-color="primaryColor" @click="handleClose" />
<icon-close :is-active="isActive" :primary-color="primaryColor" @click="handleClose" />
</div>
<n-divider v-if="!isHover && !isActive" :vertical="true" class="absolute right-0 !bg-[#a4abb8] z-2" />
</div>
@@ -39,8 +39,11 @@ interface Props {
closable?: boolean;
/** 暗黑模式 */
darkMode?: boolean;
/** 是否是最后一个 */
isLast: boolean;
}
interface Emits {
/** 点击关闭图标 */
(e: 'close'): void;
}
withDefaults(defineProps<Props>(), {
@@ -51,10 +54,7 @@ withDefaults(defineProps<Props>(), {
isLast: false
});
const emit = defineEmits<{
/** 点击关闭图标 */
(e: 'close'): void;
}>();
const emit = defineEmits<Emits>();
const { bool: isHover, setTrue, setFalse } = useBoolean();

View File

@@ -95,9 +95,11 @@ watch([() => props.startValue, () => props.endValue], () => {
start();
}
});
watchEffect(() => {
source.value = props.startValue;
});
onMounted(() => {
if (props.autoplay) {
start();

View File

@@ -1,15 +0,0 @@
<template>
<web-site-link label="github地址" :link="link" />
</template>
<script setup lang="ts">
import WebSiteLink from '../WebSiteLink/index.vue';
interface Props {
/** github链接 */
link: string;
}
defineProps<Props>();
</script>
<style scoped></style>

View File

@@ -1,11 +1,11 @@
<template>
<div
class="relative flex-center w-18px h-18px text-14px"
:style="{ color: isPrimary ? primaryColor : defaultColor }"
:style="{ color: isActive ? primaryColor : defaultColor }"
@mouseenter="setTrue"
@mouseleave="setFalse"
>
<transition name="transition-opacity">
<transition name="fade">
<icon-mdi:close-circle v-if="isHover" key="hover" class="absolute" />
<icon-mdi:close v-else key="unhover" class="absolute" />
</transition>
@@ -17,7 +17,7 @@ import { useBoolean } from '@/hooks';
interface Props {
/** 激活状态 */
isPrimary?: boolean;
isActive?: boolean;
/** 主题颜色 */
primaryColor?: string;
/** 默认颜色 */
@@ -26,7 +26,7 @@ interface Props {
withDefaults(defineProps<Props>(), {
isPrimary: false,
primaryColor: '#409EFF',
primaryColor: '#1890ff',
defaultColor: '#9ca3af'
});

View File

@@ -9,14 +9,16 @@ import { watch } from 'vue';
import { useImageVerify } from '@/hooks';
interface Props {
code: string;
code?: string;
}
interface Emits {
(e: 'update:code', code: string): void;
}
const props = defineProps<Props>();
const props = withDefaults(defineProps<Props>(), {
code: ''
});
const emit = defineEmits<Emits>();

View File

@@ -1,290 +0,0 @@
<template>
<div ref="mousewheelRef" class="h-full cursor-move">
<svg ref="svgRef" class="w-full h-full select-none" @mousedown="dragStart">
<g :style="{ transform }">
<slot></slot>
</g>
</svg>
<slot name="absolute"></slot>
</div>
</template>
<script setup lang="ts">
import { reactive, ref, computed, watch, onMounted, onBeforeUnmount } from 'vue';
import type { SScaleRange, STranslate, SPosition, SCoord, SNodeSize } from '@/interface';
interface Props {
/** 缩放比例 */
scale?: number;
/** 缩放范围 */
scaleRange?: SScaleRange;
/** g标签相对于svg标签的左上角的偏移量 */
translate?: STranslate;
/** 节点尺寸 */
nodeSize?: SNodeSize;
/** 是否开启按坐标居中画布 */
centerSvg?: boolean;
/** 居中的坐标 */
centerCoord?: SCoord;
}
interface Emits {
(e: 'update:scale', scale: number): void;
(e: 'update:translate', translate: STranslate): void;
}
interface SvgConfig {
/** 距离可视区左边的距离 */
left: number;
/** 距离可视区顶部的距离 */
top: number;
/** svg画布宽 */
width: number;
/** svg画布高 */
height: number;
}
interface DragScale {
/** 画布缩放比例 */
scale: number;
/** 画布缩放比例取值范围 */
scaleRange: SScaleRange;
/** 画布移动距离 */
translate: STranslate;
/** 是否在拖动 */
isDragging: boolean;
/** 拖动前的鼠标距离可视区左边和上边的距离 */
lastPosition: SPosition;
/** svg的属性 */
svgConfig: SvgConfig;
}
interface WheelDelta {
wheelDelta: number;
wheelDeltaX: number;
wheelDeltaY: number;
}
type DOMWheelEvent = WheelEvent & WheelDelta;
type WheelDirection = 'up' | 'down';
const props = withDefaults(defineProps<Props>(), {
scale: 1,
scaleRange: () => [0.2, 3],
translate: () => ({ x: 0, y: 0 }),
nodeSize: () => ({ w: 100, h: 100 }),
centerSvg: false,
centerCoord: () => ({ x: 0, y: 0 })
});
const emit = defineEmits<Emits>();
/** 最外层容器,用于鼠标滚轮事件 */
const mousewheelRef = ref<HTMLElement>();
/** 基本属性 */
const dragScale = reactive<DragScale>({
scale: props.scale,
scaleRange: [...props.scaleRange],
translate: { ...props.translate },
isDragging: false,
lastPosition: {
x: 0,
y: 0
},
svgConfig: {
left: 0,
top: 0,
width: 1000,
height: 500
}
});
function setDragScale(data: Partial<DragScale>) {
Object.assign(dragScale, data);
}
/** svg dom */
const svgRef = ref<SVGElement | null>(null);
function initSvgConfig() {
if (svgRef.value) {
const { left, top, width, height } = svgRef.value.getBoundingClientRect();
setDragScale({ svgConfig: { left, top, width, height } });
}
}
/** 缩放和平移样式 */
const transform = computed(() => {
const { scale, translate } = dragScale;
const { x, y } = translate;
return `translate(${x}px, ${y}px) scale(${scale})`;
});
/**
* 更新偏移量
* @param delta - 偏移量的增量
*/
function updateTranslate(delta: STranslate) {
const { x, y } = dragScale.translate;
const update = { x: x + delta.x, y: y + delta.y };
setDragScale({ translate: update });
}
/**
* 缩放后将视图移动到鼠标的位置
* @param mouseX - 鼠标x坐标
* @param mouseY - 鼠标y坐标
* @param oldScale - 缩放前的缩放比例
*/
function correctTranslate(mouseX: number, mouseY: number, oldScale: number) {
const { scale, translate } = dragScale;
const { x, y } = translate;
const sourceCoord = {
x: (mouseX - x) / oldScale,
y: (mouseY - y) / oldScale
};
const sourceTranslate = {
x: sourceCoord.x * (1 - scale),
y: sourceCoord.y * (1 - scale)
};
const update = {
x: sourceTranslate.x - (sourceCoord.x - mouseX),
y: sourceTranslate.y - (sourceCoord.y - mouseY)
};
setDragScale({ translate: update });
}
// 拖拽事件
/** 拖拽开始 */
function dragStart(e: MouseEvent) {
if (e.button !== 0) {
// 只允许鼠标点击左键拖动
return;
}
const { clientX: x, clientY: y } = e;
setDragScale({ isDragging: true, lastPosition: { x, y } });
}
/** 拖拽中 */
function dragMove(e: MouseEvent) {
if (dragScale.isDragging) {
const { clientX: x, clientY: y } = e; // 当前鼠标的位置
const { x: lX, y: lY } = dragScale.lastPosition; // 上一次鼠标的位置
const delta = { x: x - lX, y: y - lY }; // 鼠标的偏移量
updateTranslate(delta);
setDragScale({ lastPosition: { x, y } });
}
}
/** 拖拽结束 */
function dragEnd() {
setDragScale({ isDragging: false });
}
/** 缩放事件 */
function handleScale(e: WheelEvent, direction: WheelDirection) {
const { clientX, clientY } = e;
const { left, top } = dragScale.svgConfig;
const mouseX = clientX - left;
const mouseY = clientY - top;
const { scale: oldScale, scaleRange } = dragScale;
const [min, max] = scaleRange;
const scaleParam = 0.045;
const updateParam = direction === 'up' ? 1 + scaleParam : 1 - scaleParam;
const newScale = oldScale * updateParam;
if (newScale >= min && newScale <= max) {
dragScale.scale = newScale;
} else {
dragScale.scale = newScale < min ? min : max;
}
correctTranslate(mouseX, mouseY, oldScale);
}
/** 鼠标滚轮缩放事件 */
function handleMousewheel(e: WheelEvent) {
e.preventDefault();
const direction: WheelDirection = (e as DOMWheelEvent).wheelDeltaY > 0 ? 'up' : 'down';
handleScale(e, direction);
}
/** 监听拖拽事件 */
function initDragEventListener() {
window.addEventListener('mousemove', dragMove);
window.addEventListener('mouseup', dragEnd);
}
/** 监听鼠标滚轮事件 */
function initMousewheelEventListener() {
if (mousewheelRef.value) {
mousewheelRef.value.addEventListener('wheel', handleMousewheel);
}
}
/** 卸载监听事件 */
function destroyEventListener() {
window.removeEventListener('mousemove', dragMove);
window.removeEventListener('mouseup', dragEnd);
if (mousewheelRef.value) {
mousewheelRef.value.removeEventListener('wheel', handleMousewheel);
}
}
// 根据指定坐标居中布局
function handleCenterSvg() {
const { x, y } = props.centerCoord;
const isCoordValid = !Number.isNaN(x) && !Number.isNaN(y);
if (props.centerSvg && isCoordValid) {
const { w, h } = props.nodeSize;
const { width, height } = dragScale.svgConfig;
const translate = { x: width / 2 - x - w / 2, y: height / 2 - y - h / 2 };
setDragScale({ translate });
} else {
setDragScale({ translate: { x: 0, y: 0 } });
}
}
// 将scale和translate进行双向数据绑定
watch(
() => props.scale,
newValue => {
setDragScale({ scale: newValue });
}
);
watch(
() => props.translate,
newValue => {
setDragScale({ translate: newValue });
}
);
watch(
() => dragScale.scale,
newValue => {
emit('update:scale', newValue);
}
);
watch(
() => dragScale.translate,
newValue => {
emit('update:translate', newValue);
}
);
// 监听centerCoord居中画布
watch([() => props.centerSvg, () => props.centerCoord], () => {
handleCenterSvg();
});
/** 初始化 */
function init() {
initDragEventListener();
initMousewheelEventListener();
initSvgConfig();
handleCenterSvg();
}
onMounted(() => {
init();
});
// 卸载监听事件
onBeforeUnmount(() => {
destroyEventListener();
});
</script>
<style scoped></style>

View File

@@ -1,124 +0,0 @@
<template>
<div class="flex-col-center select-none">
<icon-mdi-plus-circle
class="text-20px cursor-pointer"
:style="{ color: themeColor }"
@click="handleSliderValue('plus')"
/>
<div class="h-120px pr-4px">
<n-slider
v-model:value="sliderValue"
:vertical="true"
:tooltip="false"
:style="`--rail-color: #efefef;--fill-color:${themeColor};--fill-color-hover:${themeColor}`"
/>
</div>
<div class="absolute -right-40px h-20px" :style="{ bottom: sliderLabelBottom }">
{{ sliderLabel }}
</div>
<icon-mdi-minus-circle
class="text-20px cursor-pointer"
:style="{ color: themeColor }"
@click="handleSliderValue('minus')"
/>
</div>
</template>
<script setup lang="ts">
import { ref, computed, watch } from 'vue';
import { NSlider } from 'naive-ui';
import type { SScaleRange, STranslate } from '@/interface';
interface Props {
/** 主题颜色 */
themeColor: string;
/** 缩放比例 */
scale?: number;
/** 缩放范围 */
scaleRange?: SScaleRange;
}
interface Emits {
(e: 'update:scale', scale: number): void;
(e: 'update:translate', translate: STranslate): void;
}
const props = withDefaults(defineProps<Props>(), {
scale: 1,
scaleRange: () => [0.2, 3]
});
const emit = defineEmits<Emits>();
const sliderValue = ref(getSliderValue());
const sliderLabel = computed(() => formatSlider(sliderValue.value));
const sliderLabelBottom = computed(() => getSliderLabelBottom(sliderValue.value));
function getSliderValue() {
const {
scale,
scaleRange: [min, max]
} = props;
let value = 50;
if (scale - 1 >= 0) {
value = ((scale - 1) / (Number(max) - 1)) * 50 + 50;
} else {
value = ((scale - Number(min)) / (1 - Number(min))) * 50;
}
return value;
}
function getScale(sliderValue: number) {
const [min, max] = props.scaleRange;
let scale = 1;
if (sliderValue >= 50) {
scale = ((sliderValue - 50) / 50) * (Number(max) - 1) + 1;
} else {
scale = (sliderValue / 50) * (1 - Number(min)) + Number(min);
}
return scale;
}
function handleSliderValue(type: 'plus' | 'minus') {
let step = 10;
if (sliderValue.value >= 50) {
step = 5;
}
if (type === 'minus') {
step *= -1;
}
const newValue = sliderValue.value + step;
if (newValue >= 0 && newValue <= 100) {
sliderValue.value = newValue;
} else {
sliderValue.value = newValue < 0 ? 0 : 100;
}
}
function formatSlider(sliderValue: number) {
const scale = getScale(sliderValue);
const percent = `${Math.round(scale * 100)}%`;
return percent;
}
function getSliderLabelBottom(sliderValue: number) {
return `${19 + (102 * sliderValue) / 100}px`;
}
watch(sliderValue, newValue => {
const updateScale = getScale(newValue);
emit('update:scale', updateScale);
});
watch(
() => props.scale,
() => {
sliderValue.value = getSliderValue();
}
);
</script>
<style scoped>
:deep(.n-slider-rail) {
width: 4px;
}
</style>

View File

@@ -1,80 +0,0 @@
<template>
<g>
<path :d="line" style="fill: none" :style="lineStyle"></path>
<path :d="arrow" style="stroke-width: 0" :style="arrowStyle"></path>
</g>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import type { SCoord } from '@/interface';
interface Props {
/** 边的起始坐标 */
sourceCoord: SCoord;
/** 边的终点坐标 */
targetCoord: SCoord;
/** 边的线宽 */
width?: number;
/** 填充颜色 */
color?: string;
/** 是否高亮 */
highlight?: boolean;
/** 高亮的颜色 */
highlightColor?: string;
/** 是否显示终点箭头 */
showArrow?: boolean;
}
const props = withDefaults(defineProps<Props>(), {
width: 2,
color: '#000',
highlight: false,
highlightColor: '#f00',
showArrow: true
});
const line = computed(() => {
const {
sourceCoord: { x: sX, y: sY },
targetCoord: { x: tX, y: tY },
showArrow
} = props;
const horizontalGap = Math.abs(sX - tX);
const start = `M${sX} ${sY}`;
const end = showArrow ? `${tX - 5} ${tY}` : `${tX} ${tY}`;
const control1 = `C${sX + (horizontalGap * 2) / 3} ${sY}`;
const control2 = `${tX - (horizontalGap * 2) / 3} ${tY}`;
return `${start} ${control1} ${control2} ${end}`;
});
const arrow = computed(() => {
const { x, y } = props.targetCoord;
const M = `M${x - 10} ${y}`;
const L1 = `L ${x - 10} ${y - 5 + 0.2472}`;
const A1 = `A 4 4 0 0 1 ${x - 10 + 0.178885} ${y - 5 + 0.08944}`;
const L2 = `L ${x - 0.8944} ${y - 0.4472}`;
const A2 = `A 5 5 0 0 1 ${x - 0.8944} ${y + 0.4472}`;
const L3 = `L ${x - 10 + 0.178885} ${y + 5 - 0.08944}`;
const A3 = `A 4 4 0 0 1 ${x - 10} ${y + 5 - 0.2472}`;
return `${M} ${L1} ${A1} ${L2} ${A2} ${L3} ${A3}`;
});
const lineStyle = computed(() => {
const { highlight, highlightColor, color } = props;
const stroke = highlight ? highlightColor : color;
return {
stroke,
strokeWidth: props.width
};
});
const arrowStyle = computed(() => {
const { highlight, highlightColor, color } = props;
const fill = highlight ? highlightColor : color;
return {
fill
};
});
</script>
<style scoped></style>

View File

@@ -1,28 +0,0 @@
<template>
<g :transform="transform">
<foreignObject :width="props.size.w" :height="props.size.h">
<slot></slot>
</foreignObject>
</g>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import type { SNodeSize } from '@/interface';
interface Props {
/** 节点尺寸 */
size?: SNodeSize;
/** 节点坐标 */
x: number;
/** 节点坐标 */
y: number;
}
const props = withDefaults(defineProps<Props>(), {
size: () => ({ w: 100, h: 100 })
});
const transform = computed(() => `translate(${props.x}, ${props.y})`);
</script>
<style scoped></style>

View File

@@ -1,6 +0,0 @@
import DragScaleSvg from './DragScaleSvg.vue';
import SvgNode from './SvgNode.vue';
import SvgEdge from './SvgEdge.vue';
import ScaleSlider from './ScaleSlider.vue';
export { DragScaleSvg, SvgNode, SvgEdge, ScaleSlider };

View File

@@ -1,123 +0,0 @@
<template>
<drag-scale-svg
v-model:scale="dragScaleConfig.scale"
v-model:translate="dragScaleConfig.translate"
:scale-range="dragScaleConfig.scaleRange"
:center-svg="centerSvg"
:center-coord="centerCoord"
>
<g>
<svg-edge
v-for="(edge, index) in allEdges.unhighlights"
:key="`unhighlight${index}`"
v-bind="edge"
:color="edgeColor"
/>
<svg-edge
v-for="(edge, index) in allEdges.highlights"
:key="`highlight${index}`"
v-bind="edge"
:color="edgeColor"
:highlight="true"
:highlight-color="highlightColor"
/>
</g>
<g>
<svg-node v-for="node in nodes" :key="node.id" :x="node.x" :y="node.y" :size="nodeSize">
<slot name="node" v-bind="node"></slot>
</svg-node>
</g>
<template #absolute>
<scale-slider
v-model:scale="dragScaleConfig.scale"
v-model:translate="dragScaleConfig.translate"
:theme-color="sliderColor"
class="absolute bottom-56px transition-right duration-300 ease-in-out"
:style="{ right: sliderRight + 'px' }"
/>
</template>
</drag-scale-svg>
</template>
<script setup lang="ts">
import { reactive, computed } from 'vue';
import type { SScaleRange, STranslate, SGraphNode, SGraphEdge, SNodeSize, SCoord } from '@/interface';
import { DragScaleSvg, SvgNode, SvgEdge, ScaleSlider } from './components';
interface Props {
/** 图的节点 */
nodes: SGraphNode[];
/** 图的关系线 */
edges: SGraphEdge[];
/** 节点尺寸 */
nodeSize?: SNodeSize;
/** 边的填充颜色 */
edgeColor?: string;
/** 高亮颜色 */
highlightColor?: string;
/** 需要高亮关系线的节点坐标 */
highlightCoord?: SCoord;
/** 锁放条的颜色 */
sliderColor?: string;
/** 缩放条距离父元素右边的距离 */
sliderRight?: number;
/** 是否开启按坐标居中画布 */
centerSvg?: boolean;
/** 居中的坐标 */
centerCoord?: SCoord;
}
/** 可缩放拖拽容器的配置属性 */
interface GragScaleConfig {
/** 缩放比例 */
scale: number;
/** 缩放范围 */
scaleRange: SScaleRange;
/** g标签相对于svg标签的左上角的偏移量 */
translate: STranslate;
}
const props = withDefaults(defineProps<Props>(), {
nodeSize: () => ({ w: 100, h: 100 }),
edgeColor: '#000',
highlightColor: '#fadb14',
highlightCoord: () => ({ x: NaN, y: NaN }),
sliderColor: '#000',
sliderRight: 48,
centerSvg: false,
centerCoord: () => ({ x: 0, y: 0 })
});
const dragScaleConfig = reactive<GragScaleConfig>({
scale: 1,
scaleRange: [0.2, 3],
translate: { x: 0, y: 0 }
});
/** 区分是否高亮的边 */
const allEdges = computed(() => {
const {
edges,
highlightCoord: { x: hX, y: hY },
nodeSize: { w, h }
} = props;
const highlights: SGraphEdge[] = [];
const unhighlights: SGraphEdge[] = [];
edges.forEach(edge => {
const { x: sX, y: sY } = edge.sourceCoord;
const { x: tX, y: tY } = edge.targetCoord;
const isSourceHighlight = hX === sX - w && hY + h / 2 === sY;
const isTargetHighlight = hX === tX && hY + h / 2 === tY;
if (isSourceHighlight || isTargetHighlight) {
highlights.push(edge);
} else {
unhighlights.push(edge);
}
});
return {
highlights,
unhighlights
};
});
</script>
<style scoped></style>

View File

@@ -1,40 +0,0 @@
<template>
<hover-container class="w-40px h-full text-14px text-[#999] hover:text-primary" @click="toggleDarkMode">
<icon-mdi-moon-waning-crescent v-if="dark" />
<icon-mdi-white-balance-sunny v-else />
</hover-container>
</template>
<script lang="ts" setup>
import { watch } from 'vue';
import { HoverContainer } from '../../common';
import { useBoolean } from '@/hooks';
interface Props {
/** 暗黑模式 */
dark?: boolean;
}
interface Emits {
(e: 'update', darkMode: boolean): void;
}
const props = withDefaults(defineProps<Props>(), {
dark: false
});
const emit = defineEmits<Emits>();
const { bool: darkMode, setBool: setDarkMode, toggle: toggleDarkMode } = useBoolean(props.dark);
watch(
() => props.dark,
newValue => {
setDarkMode(newValue);
}
);
watch(darkMode, newValue => {
emit('update', newValue);
});
</script>
<style scoped></style>

View File

@@ -1,20 +0,0 @@
<template>
<p>
<span>{{ label }}</span>
<a class="text-blue-500" :href="link" target="_blank">
{{ link }}
</a>
</p>
</template>
<script setup lang="ts">
interface Props {
/** 网址名称 */
label: string;
/** 网址链接 */
link: string;
}
defineProps<Props>();
</script>
<style scoped></style>

View File

@@ -1,11 +1,7 @@
import CountTo from './CountTo/index.vue';
import IconClose from './IconClose/index.vue';
import BetterScroll from './BetterScroll/index.vue';
import ButtonTab from './ButtonTab/index.vue';
import ChromeTab from './ChromeTab/index.vue';
import BetterScroll from './BetterScroll/index.vue';
import WebSiteLink from './WebSiteLink/index.vue';
import GithubLink from './GithubLink/index.vue';
import ThemeSwitch from './ThemeSwitch/index.vue';
import CountTo from './CountTo/index.vue';
import ImageVerify from './ImageVerify/index.vue';
export { CountTo, IconClose, ButtonTab, ChromeTab, BetterScroll, WebSiteLink, GithubLink, ThemeSwitch, ImageVerify };
export { BetterScroll, ButtonTab, ChromeTab, CountTo, ImageVerify };

View File

@@ -1,3 +1,5 @@
export * from './common';
export * from './custom';
export * from './svg';
export * from './custom';
export * from './common';
export * from './business';

View File

@@ -3,5 +3,6 @@ import SvgNotFound from './SvgNotFound.vue';
import SvgServiceError from './SvgServiceError.vue';
import SvgEmptyData from './SvgEmptyData.vue';
import SvgNetworkError from './SvgNetworkError.vue';
import SvgBanner from './SvgBanner.vue';
export { SvgNoPermission, SvgNotFound, SvgServiceError, SvgEmptyData, SvgNetworkError };
export { SvgNoPermission, SvgNotFound, SvgServiceError, SvgEmptyData, SvgNetworkError, SvgBanner };

View File

@@ -1 +0,0 @@
export * from './login';

View File

@@ -1,57 +0,0 @@
import { useAuthStore } from '@/store';
import { useLoading } from '@/hooks';
import { setToken, setRefreshToken, setUserInfo, consoleLog } from '@/utils';
import type { LoginToken, UserInfo } from '@/interface';
import { useRouterPush, useRouteQuery } from '../common';
export function useLogin() {
const auth = useAuthStore();
const { setAuthState } = useAuthStore();
const { toLoginRedirect } = useRouterPush();
const { loginRedirect } = useRouteQuery();
const { loading, startLoading, endLoading } = useLoading();
/**
* 登录注册
* @param param - 请求参数
* - phone: 手机号
* - pwdOrCode: 密码或验证码
* - type: 登录方式: pwd - 密码登录; sms - 验证码登录
* @returns 是否登录成功
*/
async function login(param: { phone: string; pwdOrCode: string; type: 'pwd' | 'sms' }) {
consoleLog(param); // 打印参数(接入接口后去除)
startLoading();
// 1.这里调用登录接口获取token和refreshToken
const loginToken: LoginToken = {
token: 'temp-token',
refreshToken: 'temp-refresh-token'
};
const { token, refreshToken } = loginToken;
setToken(token);
setRefreshToken(refreshToken);
// 2.这里调用获取用户信息的接口
const userInfo: UserInfo = {
userId: 'temp-user-id',
userName: 'Soybean',
userPhone: '15170283876'
};
setUserInfo(userInfo);
setAuthState({ token, userInfo });
// 3.登录成功后跳转重定向地址
toLoginRedirect(loginRedirect.value);
window.$notification?.success({
title: '登录成功!',
content: `欢迎回来,${auth.userInfo.userName}!`,
duration: 3000
});
endLoading();
}
return {
loading,
login
};
}

View File

@@ -1,5 +1,3 @@
export * from './route';
export * from './router';
export * from './system';
export * from './router';
export * from './layout';
export * from './theme';

View File

@@ -1,82 +1,70 @@
import { ref, computed, watch } from 'vue';
import type { ScrollbarInst } from 'naive-ui';
import { useThemeStore, useAppStore } from '@/store';
import { useRouteProps } from './route';
import { computed } from 'vue';
import { useAppStore, useThemeStore } from '@/store';
import type { ThemeLayoutMode, GlobalHeaderProps } from '@/interface';
export function useLayoutConfig() {
const theme = useThemeStore();
type LayoutHeaderProps = Record<ThemeLayoutMode, GlobalHeaderProps>;
export function useBasicLayout() {
const app = useAppStore();
const { setScrollbarInstance } = useAppStore();
const routeProps = useRouteProps();
const theme = useThemeStore();
/** 反转sider */
const siderInverted = computed(() => theme.navStyle.theme !== 'light');
/** 侧边菜单宽度 */
const siderMenuWidth = computed(() => {
const { collapsed } = app.menu;
const { collapsedWidth, width } = theme.menuStyle;
return collapsed ? collapsedWidth : width;
type LayoutMode = 'vertical' | 'horizontal';
const mode = computed(() => {
const vertical: LayoutMode = 'vertical';
const horizontal: LayoutMode = 'horizontal';
return theme.layout.mode.includes(vertical) ? vertical : horizontal;
});
/** 反转header */
const headerInverted = computed(() => (theme.navStyle.theme !== 'dark' ? siderInverted.value : !siderInverted.value));
/** 头部定位 */
const headerPosition = computed(() => (theme.fixedHeaderAndTab ? 'absolute' : 'static'));
/** 全局头部的高度(px) */
const headerHeight = computed(() => `${theme.headerStyle.height}px`);
/** 多页签Tab的高度(px) */
const multiTabHeight = computed(() => `${theme.multiTabStyle.height}px`);
/** 全局头部和多页签的总高度 */
const headerAndMultiTabHeight = computed(() => {
const {
multiTabStyle: { visible, height: tabHeight },
headerStyle: { height: headerHeight }
} = theme;
const height = visible ? headerHeight + tabHeight : headerHeight;
return `${height}px`;
});
/** 全局侧边栏的样式 */
const globalSiderClassAndStyle = {
class: 'transition-all duration-300 ease-in-out',
style: 'z-index:12;box-shadow: 2px 0 8px 0 rgb(29 35 41 / 5%);'
const layoutHeaderProps: LayoutHeaderProps = {
vertical: {
showLogo: false,
showHeaderMenu: false,
showMenuCollape: true
},
'vertical-mix': {
showLogo: false,
showHeaderMenu: false,
showMenuCollape: false
},
horizontal: {
showLogo: true,
showHeaderMenu: true,
showMenuCollape: false
},
'horizontal-mix': {
showLogo: true,
showHeaderMenu: false,
showMenuCollape: true
}
};
/** 纵向flex布局样式 */
const flexColumnStyle = 'display:flex;flex-direction:column;height:100%;';
const headerProps = computed(() => layoutHeaderProps[theme.layout.mode]);
/** scrollbar的content的样式 */
const scrollbarContentStyle = computed(() => {
const { fullPage } = routeProps.value;
const height = fullPage ? '100%' : 'auto';
return `display:flex;flex-direction:column;height:${height};min-height:100%;`;
});
/** 滚动条实例 */
const scrollbar = ref<ScrollbarInst | null>(null);
watch(scrollbar, newValue => {
if (newValue) {
setScrollbarInstance(newValue);
const siderVisible = computed(() => theme.layout.mode !== 'horizontal');
const siderWidth = computed(() => {
const { width, mixWidth, mixChildMenuWidth } = theme.sider;
const isVerticalMix = theme.layout.mode === 'vertical-mix';
let w = isVerticalMix ? mixWidth : width;
if (isVerticalMix && app.mixSiderFixed) {
w += mixChildMenuWidth;
}
return w;
});
const siderCollapsedWidth = computed(() => {
const { collapsedWidth, mixCollapsedWidth, mixChildMenuWidth } = theme.sider;
const isVerticalMix = theme.layout.mode === 'vertical-mix';
let w = isVerticalMix ? mixCollapsedWidth : collapsedWidth;
if (isVerticalMix && app.mixSiderFixed) {
w += mixChildMenuWidth;
}
return w;
});
return {
siderInverted,
siderMenuWidth,
headerInverted,
headerPosition,
headerHeight,
multiTabHeight,
headerAndMultiTabHeight,
globalSiderClassAndStyle,
flexColumnStyle,
scrollbarContentStyle,
scrollbar
mode,
headerProps,
siderVisible,
siderWidth,
siderCollapsedWidth
};
}

View File

@@ -1,94 +0,0 @@
import { computed, watch } from 'vue';
import type { WatchOptions } from 'vue';
import { useRoute } from 'vue-router';
import { routeName } from '@/router';
import type { RouteKey } from '@/interface';
/**
* 路由属性
*/
export function useRouteProps() {
const route = useRoute();
const props = computed(() => {
/** 路由名称 */
const name = route.name as string;
/** 缓存页面 */
const keepAlive = Boolean(route.meta?.keepAlive);
/** 视高100% */
const fullPage = Boolean(route.meta?.fullPage);
return {
name,
keepAlive,
fullPage
};
});
return props;
}
/**
* 路由查询参数
*/
export function useRouteQuery() {
const route = useRoute();
/** 登录跳转链接 */
const loginRedirect = computed(() => {
let url: string | undefined;
if (route.name === routeName('login')) {
url = (route.query?.redirect as string) || '';
}
return url;
});
return {
loginRedirect
};
}
/**
* 路由名称变化后的回调
* @param callback
*/
export function routeNameWatcher(callback: (name: RouteKey) => void, options?: WatchOptions) {
const route = useRoute();
watch(
() => route.name,
newValue => {
callback(newValue as RouteKey);
},
options
);
}
/**
* 路由全路径变化后的回调
* @param callback
*/
export function routeFullPathWatcher(callback: (fullPath: string) => void, options?: WatchOptions) {
const route = useRoute();
watch(
() => route.fullPath,
newValue => {
callback(newValue);
},
options
);
}
/**
* 路由路径变化后的回调
* @param callback - 回调函数
* @param options - 监听配置
*/
export function routePathWatcher(callback: (path: string) => void, options?: WatchOptions) {
const route = useRoute();
watch(
() => route.path,
newValue => {
callback(newValue);
},
options
);
}

View File

@@ -1,21 +1,20 @@
import { unref } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { useRouter } from 'vue-router';
import type { RouteLocationRaw } from 'vue-router';
import { router as globalRouter, routeName, ROUTE_HOME } from '@/router';
import type { LoginModuleType } from '@/interface';
import { router as globalRouter, routeName } from '@/router';
import { LoginModuleKey } from '@/interface';
/**
* 路由跳转
* @param inSetup - 是否在vue页面/组件的setup里面调用
* @param inSetup - 是否在vue页面/组件的setup里面调用在axios里面无法使用useRouter和useRoute
*/
export function useRouterPush(inSetup: boolean = true) {
const router = inSetup ? useRouter() : globalRouter;
const route = inSetup ? useRoute() : unref(globalRouter.currentRoute);
const route = globalRouter.currentRoute;
/**
* 路由跳转
* @param to - 路由
* @param newTab - 在新的浏览器标签打开
* @param to - 需要跳转的路由
* @param newTab - 是否在新的浏览器Tab标签打开
*/
function routerPush(to: RouteLocationRaw, newTab = false) {
if (newTab) {
@@ -36,53 +35,41 @@ export function useRouterPush(inSetup: boolean = true) {
* @param newTab - 在新的浏览器标签打开
*/
function toHome(newTab = false) {
routerPush(ROUTE_HOME.path, newTab);
routerPush({ name: routeName('root') }, newTab);
}
/**
* 重定向地址
* - current: 取当前的path作为重定向地址
* 跳转登录页面
* @param loginModule - 展示的登录模块
* @param redirectUrl - 重定向地址(登录成功后跳转的地址),默认undefined表示取当前地址为重定向地址
*/
type LoginRedirect = 'current' | string;
/**
* 跳转登录页面(通过vue路由)
* @param module - 展示的登录模块
* @param redirect - 重定向地址(登录成功后跳转的地址)
* @param newTab - 在新的浏览器标签打开
*/
function toLogin(module: LoginModuleType = 'pwd-login', redirect: LoginRedirect = 'current', newTab = false) {
function toLogin(loginModule?: LoginModuleKey, redirectUrl?: string) {
const module: LoginModuleKey = loginModule || 'pwd-login';
const routeLocation: RouteLocationRaw = {
name: routeName('login'),
params: { module }
};
if (redirect) {
let url = redirect;
if (redirect === 'current') {
url = route.fullPath;
}
Object.assign(routeLocation, { query: { redirect: url } });
}
routerPush(routeLocation, newTab);
const redirect = redirectUrl || route.value.fullPath;
Object.assign(routeLocation, { query: { redirect } });
routerPush(routeLocation);
}
/**
* 登陆页跳转登陆页(登录模块切换)
* @param module - 展示的登录模块
* @param newTab - 在新的浏览器标签打开
* 登录页切换其他模块
* @param module - 切换后的登录模块
*/
function toCurrentLogin(module: LoginModuleType, newTab = false) {
const { query } = route;
routerPush({ name: routeName('login'), params: { module }, query: { ...query } }, newTab);
function toLoginModule(module: LoginModuleKey) {
const { query } = route.value;
routerPush({ name: routeName('login'), params: { module }, query });
}
/**
* 登录成功后跳转重定向的地址
* @param redirect - 重定向地址
*/
function toLoginRedirect(redirect?: string) {
if (redirect) {
routerPush(redirect);
function toLoginRedirect() {
const { query } = route.value;
if (query?.redirect) {
routerPush(query.redirect as string);
} else {
toHome();
}
@@ -93,7 +80,7 @@ export function useRouterPush(inSetup: boolean = true) {
routerBack,
toHome,
toLogin,
toCurrentLogin,
toLoginModule,
toLoginRedirect
};
}

View File

@@ -1,86 +0,0 @@
import { computed, watch } from 'vue';
import { darkTheme } from 'naive-ui';
import { useDark } from '@vueuse/core';
import { useThemeStore } from '@/store';
/** 系统暗黑模式 */
export function useDarkMode() {
const osDark = useDark();
const theme = useThemeStore();
const { handleDarkMode } = useThemeStore();
/** naive-ui暗黑主题 */
const naiveTheme = computed(() => (theme.darkMode ? darkTheme : undefined));
// windicss 暗黑模式
const DARK_CLASS = 'dark';
function getHtmlElement() {
return document.querySelector('html');
}
function addDarkClass() {
const html = getHtmlElement();
if (html) {
html.classList.add(DARK_CLASS);
}
}
function removeDarkClass() {
const html = getHtmlElement();
if (html) {
html.classList.remove(DARK_CLASS);
}
}
// 监听操作系统主题模式
watch(
osDark,
newValue => {
handleDarkMode(newValue);
},
{
immediate: true
}
);
// 监听主题的暗黑模式
watch(
() => theme.darkMode,
newValue => {
if (newValue) {
addDarkClass();
} else {
removeDarkClass();
}
},
{ immediate: true }
);
return {
naiveTheme
};
}
/** 更改html样式 */
export function useHtmlStyle() {
const HIDE_SCROLL_CLASS = 'overflow-hidden';
function getHtmlElement() {
return document.querySelector('html');
}
function handleHideScroll() {
const html = getHtmlElement();
if (html) {
html.classList.add(HIDE_SCROLL_CLASS);
}
}
function handleAutoScroll() {
const html = getHtmlElement();
if (html) {
html.classList.remove(HIDE_SCROLL_CLASS);
}
}
return {
handleHideScroll,
handleAutoScroll
};
}

View File

@@ -1,31 +0,0 @@
import { onMounted, onUnmounted } from 'vue';
import { useAuthStore } from '@/store';
/** 添加用户权益变更的全局点击事件监听 */
export function useAuthChangeEvent() {
const { getIsAuthChange } = useAuthStore();
function eventHandler(event: MouseEvent) {
const change = getIsAuthChange();
if (change) {
event.stopPropagation();
window.location.reload();
}
}
function addAuthChangeListener() {
document.addEventListener('click', eventHandler, { capture: true });
}
function removeAuthChangeListener() {
document.removeEventListener('click', eventHandler);
}
onMounted(() => {
addAuthChangeListener();
});
onUnmounted(() => {
removeAuthChangeListener();
});
}

View File

@@ -1,12 +0,0 @@
import { useAuthChangeEvent } from './auth';
export function useGlobalEvent() {
/** 初始化全局监听事件 */
function initGlobalEventListener() {
useAuthChangeEvent();
}
return {
initGlobalEventListener
};
}

View File

@@ -1 +0,0 @@
export * from './global';

View File

@@ -1,3 +1 @@
export * from './common';
export * from './business';
export * from './events';

View File

@@ -1,3 +1,2 @@
export * from './regexp';
export * from './service';
export * from './map-sdk';
export * from './regexp';

View File

@@ -1,9 +0,0 @@
/** 百度地图sdk地址 */
export const BAIDU_MAP_SDK_URL =
'https://api.map.baidu.com/getscript?v=3.0&ak=KSezYymXPth1DIGILRX3oYN9PxbOQQmU&services=&t=20210201100830&s=1';
/** 高德地图sdk地址 */
export const GAODE_MAP_SDK_URL = 'https://webapi.amap.com/maps?v=2.0&key=e7bd02bd504062087e6563daf4d6721d';
/** 腾讯地图sdk地址 */
export const TENCENT_MAP_SDK_URL = 'https://map.qq.com/api/gljs?v=1.exp&key=A6DBZ-KXPLW-JKSRY-ONZF4-CPHY3-K6BL7';

View File

@@ -9,8 +9,12 @@ export const REGEXP_EMAIL = /^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$/;
export const REGEXP_PWD =
/^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){8,18}$/;
/** 验证码正则(6位数字) */
export const REGEXP_CODE = /^\d{6}$/;
/** 6位数字验证码正则 */
export const REGEXP_CODE_SIX = /^\d{6}$/;
/** 图片验证码正则(4位数字) */
export const REGEXP_IMG_CODE = /^\d{4}$/;
/** 4位数字验证码正则 */
export const REGEXP_CODE_FOUR = /^\d{4}$/;
/** url链接正则 */
export const REGEXP_URL =
/(((^https?:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+(?::\d+)?|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[\w]*))?)$/;

View File

@@ -4,18 +4,21 @@ export const REQUEST_TIMEOUT = 60 * 1000;
/** 错误信息的显示时间 */
export const ERROR_MSG_DURATION = 3 * 1000;
/** 兜底的请求错误code */
/** 默认的请求错误code */
export const DEFAULT_REQUEST_ERROR_CODE = 'DEFAULT';
/** 兜底的请求错误文本 */
/** 默认的请求错误文本 */
export const DEFAULT_REQUEST_ERROR_MSG = '请求错误~';
/** 请求超时的错误code(为固定值ECONNABORTED) */
export const REQUEST_TIMEOUT_CODE = 'ECONNABORTED';
/** 请求超时的错误文本 */
export const REQUEST_TIMEOUT_MSG = '请求超时~';
/** 网络不可用的code */
export const NETWORK_ERROR_CODE = 'NETWORK_ERROR';
/** 网络不可用的错误文本 */
export const NETWORK_ERROR_MSG = '网络不可用~';
@@ -32,8 +35,12 @@ export const ERROR_STATUS = {
502: '502: 错误网关~',
503: '503: 服务不可用~',
504: '504: 网关超时~',
505: '505: http版本不支持该请求~'
505: '505: http版本不支持该请求~',
[DEFAULT_REQUEST_ERROR_CODE]: DEFAULT_REQUEST_ERROR_MSG
};
/** 不弹出错误信息的code */
export const NO_ERROR_MSG_CODE: (string | number)[] = [];
/** token失效需要刷新token的code */
export const REFRESH_TOKEN_CODE: (string | number)[] = [66666];

View File

@@ -1,8 +0,0 @@
import type { App } from 'vue';
import setupNetworkDirective from './network';
import setupLoginDirective from './login';
export function setupDirectives(app: App) {
setupNetworkDirective(app);
setupLoginDirective(app);
}

View File

@@ -1,27 +0,0 @@
import type { App, Directive } from 'vue';
import { useAuthStore } from '@/store';
import { useRouterPush } from '@/composables';
export default function setupLoginDirective(app: App) {
const auth = useAuthStore();
const { toLogin } = useRouterPush(false);
function listenerHandler(event: MouseEvent) {
if (!auth.isLogin) {
event.stopPropagation();
toLogin();
}
}
const loginDirective: Directive<HTMLElement, boolean | undefined> = {
mounted(el: HTMLElement, binding) {
if (binding.value === false) return;
el.addEventListener('click', listenerHandler, { capture: true });
},
unmounted(el: HTMLElement, binding) {
if (binding.value === false) return;
el.removeEventListener('click', listenerHandler);
}
};
app.directive('login', loginDirective);
}

View File

@@ -1,25 +0,0 @@
import type { App, Directive } from 'vue';
import { NETWORK_ERROR_MSG } from '@/config';
export default function setupNetworkDirective(app: App) {
function listenerHandler(event: MouseEvent) {
const hasNetwork = window.navigator.onLine;
if (!hasNetwork) {
window.$message?.error(NETWORK_ERROR_MSG);
event.stopPropagation();
}
}
const networkDirective: Directive<HTMLElement, boolean | undefined> = {
mounted(el: HTMLElement, binding) {
if (binding.value === false) return;
el.addEventListener('click', listenerHandler, { capture: true });
},
unmounted(el: HTMLElement, binding) {
if (binding.value === false) return;
el.removeEventListener('click', listenerHandler);
}
};
app.directive('network', networkDirective);
}

View File

@@ -1,9 +0,0 @@
/** 动画类型 */
export enum EnumAnimate {
'zoom-fade' = '渐变',
'zoom-out' = '闪现',
'fade-slide' = '滑动',
'fade' = '消退',
'fade-bottom' = '底部消退',
'fade-scale' = '缩放消退'
}

View File

@@ -1,5 +1,5 @@
export * from './system';
export * from './theme';
export * from './animate';
export * from './typeof';
export * from './storage';
export * from './service';
export * from './system';
export * from './theme';

View File

@@ -0,0 +1,6 @@
/** http请求头的content-type类型 */
export enum ContentType {
json = 'application/json',
formUrlencoded = 'application/x-www-form-urlencoded',
formData = 'multipart/form-data'
}

View File

@@ -1,4 +1,6 @@
export enum EnumStorageKey {
/** 主题颜色 */
'theme-color' = '__THEME_COLOR__',
/** 用户token */
'token' = '__TOKEN__',
/** 用户刷新token */
@@ -6,5 +8,5 @@ export enum EnumStorageKey {
/** 用户信息 */
'user-info' = '__USER_INFO__',
/** 多页签路由信息 */
'tab-route' = '__TAB_ROUTE__'
'tab-routes' = '__TAB_ROUTES__'
}

View File

@@ -1,10 +1,3 @@
/** http请求头的content-type类型 */
export enum ContentType {
json = 'application/json',
formUrlencoded = 'application/x-www-form-urlencoded',
formData = 'multipart/form-data'
}
/** 登录模块 */
export enum EnumLoginModule {
'pwd-login' = '账密登录',

View File

@@ -1,27 +1,30 @@
/** 导航模式 */
export enum EnumNavMode {
/** 布局模式 */
export enum EnumThemeLayoutMode {
'vertical' = '左侧菜单模式',
'horizontal' = '顶部菜单模式',
'vertical-mix' = '左侧菜单混合模式',
'horizontal-mix' = '顶部菜单混合模式'
}
/** 导航风格 */
export enum EnumNavTheme {
'dark' = '暗色侧边栏',
'light' = '白色侧边栏',
'header-dark' = '暗色的侧边栏和顶栏'
}
/** 多页签风格 */
export enum EnumMultiTabMode {
'button' = '按钮风格',
'chrome' = '谷歌风格'
export enum EnumThemeTabMode {
'chrome' = '谷歌风格',
'button' = '按钮风格'
}
/** 水平模式的菜单位置 */
export enum EnumHorizontalMenuPosition {
export enum EnumThemeHorizontalMenuPosition {
'flex-start' = '居左',
'center' = '居中',
'flex-end' = '居右'
}
/** 过渡动画类型 */
export enum EnumThemeAnimateMode {
'zoom-fade' = '渐变',
'zoom-out' = '闪现',
'fade-slide' = '滑动',
'fade' = '消退',
'fade-bottom' = '底部消退',
'fade-scale' = '缩放消退'
}

View File

@@ -1,9 +1,5 @@
import useAntv from './useAntv';
import useAntvTool from './useAntvTool';
import useCountDown from './useCountDown';
import useSmsCode from './useSmsCode';
import useImageVerify from './useImageVerify';
import useAgreement from './useAgreement';
import useVirtualList from './useVirtualList';
export { useAntv, useAntvTool, useCountDown, useSmsCode, useImageVerify, useAgreement, useVirtualList };
export { useCountDown, useSmsCode, useImageVerify };

View File

@@ -1,20 +0,0 @@
import { ref } from 'vue';
/** 使用勾选协议 */
export default function useAgreement(text = '请勾选 "我已经仔细阅读并接受《用户协议》《隐私权政策》"') {
const agreement = ref(true);
function isAgree() {
let agree = true;
if (!agreement.value) {
agree = false;
window.$message?.error(text);
}
return agree;
}
return {
agreement,
isAgree
};
}

View File

@@ -1,53 +0,0 @@
import { ref, watch, onMounted } from 'vue';
import type { ComputedRef } from 'vue';
import type { Plot } from '@antv/g2plot';
import { useBoolean } from '@/hooks';
interface AntvFn<T, O> {
new (dom: HTMLElement, options: O): T;
}
export default function useAntv<GraphOption, GraphType extends Plot<GraphOption>>(
GraphFn: AntvFn<GraphType, GraphOption>,
graphOptions: ComputedRef<GraphOption>
) {
/** 图表dom容器 */
const domRef = ref<HTMLElement>();
/** 图表实例 */
const graph = ref<GraphType>();
/** 是否可以开始渲染图表 */
const { bool: canRender, setTrue: setCanRender } = useBoolean();
/** 是否是在onMouted第一次渲染图表 */
const { bool: isFirstRender, setTrue: setIsFirstRender, setFalse: setIsNotFirstRender } = useBoolean();
/** 渲染图表 */
function renderGraph(options: GraphOption) {
if (!domRef.value) return;
if (!graph.value) {
graph.value = new GraphFn(domRef.value, options);
graph.value.render();
} else {
graph.value.update(options);
}
}
onMounted(() => {
setCanRender();
setIsFirstRender();
renderGraph(graphOptions.value);
setIsNotFirstRender();
});
watch(graphOptions, newValue => {
if (!canRender.value || isFirstRender.value) return;
renderGraph(newValue);
});
return {
domRef,
graph
};
}

View File

@@ -1,31 +0,0 @@
export default function useAntvTool() {
/**
* antv滑动调属性
*/
function getSlider(columns: number, length: number, sliderColor: string) {
return {
start: 1 - columns / length,
end: 1,
foregroundStyle: { fill: sliderColor }
};
}
function getFormatter(unit: string) {
const EMPTY = ' ';
function formatter(v: number | null) {
return v === null ? EMPTY : v + unit;
}
return formatter;
}
function formatLabelWithUnit(value: number | null, unit: string) {
const EMPTY = ' ';
return value === null ? EMPTY : value + unit;
}
return {
getSlider,
getFormatter,
formatLabelWithUnit
};
}

View File

@@ -1,12 +1,24 @@
import { computed } from 'vue';
import { REGEXP_PHONE } from '@/config';
import { fetchSmsCode } from '@/service';
import { useLoading } from '../common';
import useCountDown from './useCountDown';
export default function useSmsCode() {
const { loading, startLoading, endLoading } = useLoading();
const { counts, start, isCounting } = useCountDown(60);
const initLabel = '获取验证码';
const countingLabel = (second: number) => `${second}秒后重新获取`;
const label = computed(() => (isCounting.value ? countingLabel(counts.value) : initLabel));
const label = computed(() => {
let text = initLabel;
if (loading.value) {
text = '';
}
if (isCounting.value) {
text = countingLabel(counts.value);
}
return text;
});
/** 判断手机号码格式是否正确 */
function isPhoneValid(phone: string) {
@@ -27,16 +39,21 @@ export default function useSmsCode() {
*/
async function getSmsCode(phone: string) {
const valid = isPhoneValid(phone);
if (!valid) return;
// 该处调用验证码接口
window.$message?.success('验证码发送成功!');
start();
if (!valid || loading.value) return;
startLoading();
const { data } = await fetchSmsCode(phone);
if (data) {
window.$message?.success('验证码发送成功!');
start();
}
endLoading();
}
return {
label,
start,
isCounting,
getSmsCode
getSmsCode,
loading
};
}

Some files were not shown because too many files have changed in this diff Show More