mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-10-11 20:33:41 +08:00
Compare commits
33 Commits
old-versio
...
v0.1.1
Author | SHA1 | Date | |
---|---|---|---|
|
db3c25ea14 | ||
|
5eddb4910c | ||
|
ce531ce5dd | ||
|
28efbdbc70 | ||
|
cc290accc2 | ||
|
579e07400e | ||
|
b2a4ddf5e3 | ||
|
28b5d22401 | ||
|
839b82ba8b | ||
|
09c7658c21 | ||
|
e25afe2fad | ||
|
371fad4f26 | ||
|
a090d398fc | ||
|
6d132c5977 | ||
|
912bfdf439 | ||
|
bf020a8258 | ||
|
10e4d81bd6 | ||
|
0653fb144f | ||
|
006467a062 | ||
|
0c5770dfd2 | ||
|
0e783bcf7b | ||
|
e5793e1c8d | ||
|
85b55bb37a | ||
|
b36a62b150 | ||
|
c804b21ceb | ||
|
5bfb8199b4 | ||
|
ab9a6a2f39 | ||
|
b93b80cb4b | ||
|
f5a36a05cb | ||
|
035fa114c9 | ||
|
2c196841bd | ||
|
0d2a5629e8 | ||
|
de2057f141 |
8
.env
8
.env
@@ -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
21
.env-config.ts
Normal 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'
|
||||
}
|
||||
};
|
@@ -1,5 +0,0 @@
|
||||
#请求的环境
|
||||
VITE_HTTP_ENV=DEV
|
||||
|
||||
#请求地址
|
||||
VITE_HTTP_URL=https://test.aisuit.com.cn
|
@@ -1,5 +0,0 @@
|
||||
#请求的环境 正式环境
|
||||
VITE_HTTP_ENV=PROD
|
||||
|
||||
#请求地址
|
||||
VITE_HTTP_URL=http://192.168.100.43:8201
|
@@ -1,4 +0,0 @@
|
||||
VITE_HTTP_ENV=STAGING
|
||||
|
||||
#请求地址
|
||||
VITE_HTTP_URL=http://192.168.100.43:8201
|
@@ -11,4 +11,4 @@ lib
|
||||
/docs
|
||||
.vscode
|
||||
.local
|
||||
index.html
|
||||
!.env-config.ts
|
||||
|
138
.eslintrc.js
138
.eslintrc.js
@@ -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
20
.gitignore
vendored
@@ -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
1
.husky/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
_
|
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
pnpm lint:fix && pnpm vtsc
|
||||
pnpm lint && pnpm typecheck
|
||||
|
@@ -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'
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@@ -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",
|
||||
|
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -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",
|
||||
|
167
CHANGELOG.md
167
CHANGELOG.md
@@ -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页面:403,404,500 ([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
21
LICENSE
@@ -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.
|
@@ -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)
|
||||
|
||||
## 简介
|
||||
|
||||
Soybean Admin 是一个基于 Vue3、Vite、Naive UI、TypeScript 的免费中后台模版,它使用了最新的前端技术栈,内置丰富的插件,有着极高的代码规范,开箱即用的中后台前端解决方案,也可用于学习参考。
|
||||
Soybean Admin Thin 是Soybean Admin的精简版。
|
||||
|
||||
## 特性
|
||||
|
||||
|
@@ -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
5
build/env/index.ts
vendored
@@ -1,5 +0,0 @@
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
const { parsed: viteEnv } = dotenv.config(); // 加载环境
|
||||
|
||||
export default viteEnv!;
|
@@ -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';
|
||||
|
@@ -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
|
||||
}
|
||||
})
|
||||
];
|
||||
};
|
||||
|
@@ -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' })
|
||||
|
@@ -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;
|
||||
}
|
||||
|
30
index.html
30
index.html
@@ -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>
|
||||
|
@@ -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;
|
||||
|
@@ -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
211
mock/api/route.ts
Normal 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;
|
96
package.json
96
package.json
@@ -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
2890
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
}
|
||||
|
44
public/resource/loading.js
Normal file
44
public/resource/loading.js
Normal 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 |
27
src/App.vue
27
src/App.vue
@@ -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>
|
||||
|
@@ -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 |
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 8.6 KiB |
@@ -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 |
48
src/components/business/LoginAgreement/index.vue
Normal file
48
src/components/business/LoginAgreement/index.vue
Normal 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>
|
@@ -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
@@ -1,5 +0,0 @@
|
||||
import Banner1 from './Banner1.vue';
|
||||
import Banner2 from './Banner2.vue';
|
||||
import Banner3 from './Banner3.vue';
|
||||
|
||||
export { Banner1, Banner2, Banner3 };
|
@@ -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>
|
10
src/components/common/DarkModeContainer/index.vue
Normal file
10
src/components/common/DarkModeContainer/index.vue
Normal 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>
|
39
src/components/common/DarkModeSwitch/index.vue
Normal file
39
src/components/common/DarkModeSwitch/index.vue
Normal 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>
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
||||
|
@@ -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 };
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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();
|
||||
|
||||
|
@@ -95,9 +95,11 @@ watch([() => props.startValue, () => props.endValue], () => {
|
||||
start();
|
||||
}
|
||||
});
|
||||
|
||||
watchEffect(() => {
|
||||
source.value = props.startValue;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
if (props.autoplay) {
|
||||
start();
|
||||
|
@@ -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>
|
@@ -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'
|
||||
});
|
||||
|
||||
|
@@ -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>();
|
||||
|
||||
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
@@ -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 };
|
@@ -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>
|
@@ -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>
|
@@ -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>
|
@@ -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 };
|
||||
|
@@ -1,3 +1,5 @@
|
||||
export * from './common';
|
||||
export * from './custom';
|
||||
export * from './svg';
|
||||
export * from './custom';
|
||||
export * from './common';
|
||||
export * from './business';
|
||||
|
@@ -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 };
|
||||
|
@@ -1 +0,0 @@
|
||||
export * from './login';
|
@@ -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
|
||||
};
|
||||
}
|
@@ -1,5 +1,3 @@
|
||||
export * from './route';
|
||||
export * from './router';
|
||||
export * from './system';
|
||||
export * from './router';
|
||||
export * from './layout';
|
||||
export * from './theme';
|
||||
|
@@ -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
|
||||
};
|
||||
}
|
||||
|
@@ -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
|
||||
);
|
||||
}
|
@@ -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
|
||||
};
|
||||
}
|
||||
|
@@ -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
|
||||
};
|
||||
}
|
@@ -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();
|
||||
});
|
||||
}
|
@@ -1,12 +0,0 @@
|
||||
import { useAuthChangeEvent } from './auth';
|
||||
|
||||
export function useGlobalEvent() {
|
||||
/** 初始化全局监听事件 */
|
||||
function initGlobalEventListener() {
|
||||
useAuthChangeEvent();
|
||||
}
|
||||
|
||||
return {
|
||||
initGlobalEventListener
|
||||
};
|
||||
}
|
@@ -1 +0,0 @@
|
||||
export * from './global';
|
@@ -1,3 +1 @@
|
||||
export * from './common';
|
||||
export * from './business';
|
||||
export * from './events';
|
||||
|
@@ -1,3 +1,2 @@
|
||||
export * from './regexp';
|
||||
export * from './service';
|
||||
export * from './map-sdk';
|
||||
export * from './regexp';
|
||||
|
@@ -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';
|
@@ -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]*))?)$/;
|
||||
|
@@ -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];
|
||||
|
@@ -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);
|
||||
}
|
@@ -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);
|
||||
}
|
@@ -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);
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
/** 动画类型 */
|
||||
export enum EnumAnimate {
|
||||
'zoom-fade' = '渐变',
|
||||
'zoom-out' = '闪现',
|
||||
'fade-slide' = '滑动',
|
||||
'fade' = '消退',
|
||||
'fade-bottom' = '底部消退',
|
||||
'fade-scale' = '缩放消退'
|
||||
}
|
@@ -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';
|
||||
|
6
src/enum/common/service.ts
Normal file
6
src/enum/common/service.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
/** http请求头的content-type类型 */
|
||||
export enum ContentType {
|
||||
json = 'application/json',
|
||||
formUrlencoded = 'application/x-www-form-urlencoded',
|
||||
formData = 'multipart/form-data'
|
||||
}
|
@@ -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__'
|
||||
}
|
||||
|
@@ -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' = '账密登录',
|
||||
|
@@ -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' = '缩放消退'
|
||||
}
|
||||
|
@@ -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 };
|
||||
|
@@ -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
|
||||
};
|
||||
}
|
@@ -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
|
||||
};
|
||||
}
|
@@ -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
|
||||
};
|
||||
}
|
@@ -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
Reference in New Issue
Block a user