mirror of
https://github.com/soybeanjs/soybean-admin.git
synced 2025-10-13 13:23:42 +08:00
Compare commits
23 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
8b27fc8bb8 | ||
|
738964a769 | ||
|
c429cd0293 | ||
|
20aa39f14e | ||
|
93f9aa9584 | ||
|
8ce627a397 | ||
|
1ffb75afce | ||
|
32aa5ee75a | ||
|
98a7d25cf8 | ||
|
1b3463d2e7 | ||
|
cff11d9175 | ||
|
54577f10fc | ||
|
6261156c5a | ||
|
f5a5f44a2b | ||
|
3fb7a5f709 | ||
|
e6c9b35ab4 | ||
|
0569666a8f | ||
|
e1dacdbc39 | ||
|
03b398af2f | ||
|
451c7547af | ||
|
c81221efac | ||
|
4e04a8f8ad | ||
|
9f64321d73 |
@@ -1,4 +1,4 @@
|
||||
#请求的环境
|
||||
VITE_HTTP_ENV=DEV
|
||||
#请求地址
|
||||
VITE_HTTP_URL=http://192.168.100.57/
|
||||
VITE_HTTP_URL=https://test.aisuit.com.cn
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#请求的环境 正式环境
|
||||
VITE_HTTP_ENV=PROD
|
||||
#请求地址
|
||||
VITE_HTTP_URL=http://119.23.220.176:17321
|
||||
VITE_HTTP_URL=http://192.168.100.43:8201
|
||||
|
@@ -1,3 +1,3 @@
|
||||
VITE_HTTP_ENV=STAGING
|
||||
#请求地址
|
||||
VITE_HTTP_URL=http://119.23.220.176:17321
|
||||
VITE_HTTP_URL=http://192.168.100.43:8201
|
||||
|
5
.vscode/settings.json
vendored
5
.vscode/settings.json
vendored
@@ -62,7 +62,10 @@
|
||||
"material-icon-theme.folders.associations": {
|
||||
"enum": "typescript",
|
||||
"store": "context",
|
||||
"composable": "hook",
|
||||
"composables": "hook",
|
||||
"business": "core",
|
||||
"directive": "tools",
|
||||
"directives": "tools",
|
||||
"business": "core"
|
||||
}
|
||||
}
|
||||
|
22
CHANGELOG.md
22
CHANGELOG.md
@@ -1,5 +1,27 @@
|
||||
# Changelog
|
||||
|
||||
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.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)
|
||||
|
||||
|
||||
|
53
README.md
53
README.md
@@ -1,14 +1,9 @@
|
||||
<div align="center">
|
||||
<a href="https://github.com/honghuangdc/soybean-admin">
|
||||
<img alt="SoybeanAdmin Logo" width="200" height="200" src="https://s3.bmp.ovh/imgs/2021/09/088571214c76b1e5.png">
|
||||
</a><br /><br />
|
||||
<div style="text-align:center">
|
||||
<img src="https://i.loli.net/2021/11/24/x5lLfuSnEawBAgi.png"/>
|
||||
<h1>Soybean Admin</h1>
|
||||
<br />
|
||||
</div>
|
||||
|
||||
[](LICENSE)
|
||||
|
||||
|
||||
[](./LICENSE)
|
||||
|
||||
## 简介
|
||||
|
||||
@@ -26,25 +21,26 @@ Soybean Admin 是一个基于 Vue3、Vite、Naive UI、TypeScript 的免费中
|
||||
|
||||
- [soybean-admin](https://soybean.pro/)
|
||||
|
||||
<p align="center">
|
||||
## 文档
|
||||
|
||||
<img alt="SoybeanAdmin" width="100%" src="https://raw.githubusercontent.com/honghuangdc/project-assets/main/img/02.png">
|
||||
|
||||
<img alt="SoybeanAdmin" width="100%" src="https://raw.githubusercontent.com/honghuangdc/project-assets/main/img/03.png">
|
||||
|
||||
<img alt="SoybeanAdmin" width="100%" src="https://raw.githubusercontent.com/honghuangdc/project-assets/main/img/01.png">
|
||||
|
||||
<img alt="SoybeanAdmin" width="100%" src="https://raw.githubusercontent.com/honghuangdc/project-assets/main/img/04.png">
|
||||
|
||||
<img alt="SoybeanAdmin" width="100%" src="https://raw.githubusercontent.com/honghuangdc/project-assets/main/img/05.png">
|
||||
|
||||
</p>
|
||||
- [项目文档](https://docs.soybean.pro)
|
||||
|
||||
### 代码仓库
|
||||
|
||||
**github**:https://github.com/honghuangdc/soybean-admin
|
||||
- [github](https://github.com/honghuangdc/soybean-admin)
|
||||
|
||||
**gitee**:https://gitee.com/honghuangdc/soybean-admin
|
||||
- [gitee](https://gitee.com/honghuangdc/soybean-admin)
|
||||
|
||||
## 项目示例图
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 使用 Gitpod
|
||||
|
||||
@@ -52,10 +48,6 @@ Soybean Admin 是一个基于 Vue3、Vite、Naive UI、TypeScript 的免费中
|
||||
|
||||
[](https://gitpod.io/#https://github.com/honghuangdc/soybean-admin)
|
||||
|
||||
## 文档
|
||||
|
||||
[项目相关文档](./doc)
|
||||
|
||||
## 安装使用
|
||||
|
||||
- 克隆代码
|
||||
@@ -102,9 +94,10 @@ pnpm i -g commitizen
|
||||
|
||||
支持现代浏览器, 不支持 IE
|
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/archive/internet-explorer_9-11/internet-explorer_9-11_48x48.png" alt="IE" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)Safari |
|
||||
| :-: | :-: | :-: | :-: | :-: |
|
||||
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
||||
|
||||
## 开源作者
|
||||
|
||||
[@Soybean](https://github.com/honghuangdc)
|
||||
@@ -115,11 +108,11 @@ pnpm i -g commitizen
|
||||
|
||||
- QQ 群 `711301266`
|
||||
|
||||
<div style="text-align:left;">
|
||||
<img src="https://raw.githubusercontent.com/honghuangdc/project-assets/main/img/qq_qrcode.JPG" style="width:200px" />
|
||||
<div style="text-align:left">
|
||||
<img src="https://i.loli.net/2021/11/24/1J6REWXiHomU2kM.jpg" style="width:200px" />
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
- 本人微信号:honghuangdc,欢迎来技术交流。
|
||||
|
||||
|
2
build/env/index.ts
vendored
2
build/env/index.ts
vendored
@@ -2,4 +2,4 @@ import dotenv from 'dotenv';
|
||||
|
||||
const { parsed: viteEnv } = dotenv.config(); // 加载环境
|
||||
|
||||
export default viteEnv;
|
||||
export default viteEnv!;
|
||||
|
@@ -1,47 +0,0 @@
|
||||
### 1.interface和type
|
||||
|
||||
##### interface和type使用优先级:能用interface表示的类型就用interface。
|
||||
|
||||
### 2.请求函数
|
||||
|
||||
#### api接口:
|
||||
|
||||
统一以 **fetch** 开头,例如:
|
||||
|
||||
```typescript
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @param id - 用户唯一标识id
|
||||
*/
|
||||
function fetchUserInfo(id:string) {
|
||||
// ***
|
||||
}
|
||||
/**
|
||||
* 删除列表项
|
||||
* @param id - 列表id
|
||||
*/
|
||||
function fetchDeleteListItem(id:string) {
|
||||
// ***
|
||||
}
|
||||
```
|
||||
|
||||
#### middleware中间件:
|
||||
|
||||
统一以 **handle** 开头,例如
|
||||
|
||||
```typescript
|
||||
/**接口返回的用户信息 */
|
||||
interface ResponseUserInfo {
|
||||
userId: string;
|
||||
userName: string;
|
||||
userAge: number;
|
||||
}
|
||||
/**
|
||||
* 获取用户信息 中间件
|
||||
@param data - 返回的用户信息
|
||||
*/
|
||||
function handleUserInfo(data: ResponseUserInfo): UserInfo {
|
||||
// ***
|
||||
}
|
||||
```
|
||||
|
@@ -1,35 +0,0 @@
|
||||
### css书写顺序
|
||||
|
||||
1. 定位属性:
|
||||
|
||||
`position display float left top right bottom overflow clear z-index`
|
||||
|
||||
2. 自身属性:
|
||||
|
||||
`width height padding border margin background`
|
||||
|
||||
3. 文字样式:
|
||||
|
||||
`font-family font-size font-style font-weight font-varient color`
|
||||
|
||||
4. 文本属性:
|
||||
|
||||
`text-align vertical-align text-wrap text-transform text-indent text-decoration letter-spacing word-spacing white-space text-overflow`
|
||||
|
||||
5. css3中新增属性:
|
||||
|
||||
`content box-shadow border-radius transform`
|
||||
|
||||
#### class类名的顺序:
|
||||
|
||||
1. 自定义的class类名(遵循BEM命名法)
|
||||
2. css插件提供的类名按照以上的css属性对应的顺序
|
||||
|
||||
例如:自定义类名结合tailwind css
|
||||
|
||||
<div class="demo-container absolute flex justify-center items-center left-10px top-12px overflow-hidden wh-full p-10px border-1px border-[#f00] m-24px bg-[#fff] text-32px text-[#0f0]"></div>
|
||||
<style>
|
||||
.demo-container {
|
||||
box-shadow: 2px 0 8px 0 rgb(29 35 41 / 5%);
|
||||
}
|
||||
</style>
|
@@ -1,41 +0,0 @@
|
||||
### iconify用法
|
||||
|
||||
#### 一、静态用法:直接用图标的组件名称
|
||||
|
||||
1. 安装vscode智能提示的插件: Iconify IntelliSense
|
||||
2. 找图标:网址 https://icones.js.org/ 或者 vscode安装 icones插件
|
||||
3. 确定图标名字:找到图标后复制名字 如:**'mdi:emoticon'** 组件为: `<icon-mdi:emoticon />`, icon-为设置的前缀
|
||||
4. 设置样式:同html标签一样直接应用style属性或者class属性; 通过设置color和font-size属性设置对应的颜色和大小
|
||||
|
||||
#### 二、多个图标动态渲染
|
||||
|
||||
1. 确定图标名字,如:'mdi:emoticon'
|
||||
|
||||
2. 引入Icon组件:
|
||||
|
||||
`import { Icon } from '@iconify/vue';`
|
||||
|
||||
3. 动态渲染
|
||||
|
||||
`<Icon icon="mdi:emoticon" />`
|
||||
|
||||
*ps:Icon组件属性 https://docs.iconify.design/icon-components/vue/*
|
||||
|
||||
#### 三、结合naiveUI组件动态渲染
|
||||
|
||||
1. 确定图标名字,如:**'mdi:emoticon'**
|
||||
|
||||
2. 引入vue的h函数:
|
||||
|
||||
`import { h } from 'vue';`
|
||||
|
||||
3. 引入Icon组件
|
||||
|
||||
`import { Icon } from '@iconify/vue';`
|
||||
|
||||
4. 动态渲染
|
||||
|
||||
`() => h(Icon, { icon: 'mdi:emoticon', style: { color: '#f00', fontSize: '16px' } })`
|
||||
|
||||
*ps:@/uitls已封装好了函数:iconifyRender*
|
||||
|
209
doc/vue书写规范.md
209
doc/vue书写规范.md
@@ -1,209 +0,0 @@
|
||||
### script-setup写法
|
||||
|
||||
#### 第一部分
|
||||
|
||||
##### template
|
||||
|
||||
#### 第二部分
|
||||
|
||||
##### script
|
||||
|
||||
##### 一、import的顺序, 依次按照下面的顺序。
|
||||
|
||||
1. vue模块
|
||||
|
||||
```typescript
|
||||
import { } from 'vue';
|
||||
```
|
||||
|
||||
2. vue相关类型
|
||||
|
||||
```typescript
|
||||
import type { } from 'vue';
|
||||
```
|
||||
|
||||
3. vue-router模块
|
||||
|
||||
```typescript
|
||||
import { } from 'vue-router';
|
||||
```
|
||||
|
||||
4. vue-router相关类型
|
||||
|
||||
```typescript
|
||||
import type { } from 'vue-router';
|
||||
```
|
||||
|
||||
5. UI框架模块
|
||||
|
||||
```typescript
|
||||
import { } from 'naive-ui';
|
||||
```
|
||||
|
||||
6. UI框架相关类型
|
||||
|
||||
```typescript
|
||||
import type { } from 'naive-ui';
|
||||
```
|
||||
|
||||
7. 第三方依赖
|
||||
|
||||
```typescript
|
||||
import BScroll from 'bscroll';
|
||||
```
|
||||
|
||||
8. 第三方依赖相关类型
|
||||
|
||||
```typescript
|
||||
import type { } from 'bscroll';
|
||||
```
|
||||
|
||||
9. @/enum
|
||||
|
||||
```typescript
|
||||
import { } from '@/enum';
|
||||
```
|
||||
|
||||
10. @/setting
|
||||
|
||||
```typescript
|
||||
import { } from '@/setting';
|
||||
```
|
||||
|
||||
11. @/plugins
|
||||
|
||||
```typescript
|
||||
import { } from '@/plugins';
|
||||
```
|
||||
|
||||
12. @/layouts
|
||||
|
||||
```typescript
|
||||
import { } from '@/layouts';
|
||||
```
|
||||
|
||||
13. @/views
|
||||
|
||||
```typescript
|
||||
import { } from '@/views';
|
||||
```
|
||||
|
||||
14. @/components
|
||||
|
||||
```typescript
|
||||
import { } from '@/components';
|
||||
```
|
||||
|
||||
15. @/hooks
|
||||
|
||||
```typescript
|
||||
import { } from '@/hooks';
|
||||
```
|
||||
|
||||
16. @/store
|
||||
|
||||
```typescript
|
||||
import { } from '@/store';
|
||||
```
|
||||
|
||||
17. @/context
|
||||
|
||||
```typescript
|
||||
import { } from '@/context';
|
||||
```
|
||||
|
||||
18. @/router
|
||||
|
||||
```typescript
|
||||
import { } from '@/router';
|
||||
```
|
||||
|
||||
19. @/service
|
||||
|
||||
```typescript
|
||||
import { } from '@/service';
|
||||
```
|
||||
|
||||
20. @/utils
|
||||
|
||||
```typescript
|
||||
import { } from '@/utils';
|
||||
```
|
||||
|
||||
21. @/interface
|
||||
|
||||
```typescript
|
||||
import { } from '@/interface';
|
||||
```
|
||||
|
||||
22. @/assets
|
||||
|
||||
```typescript
|
||||
import { } from '@/assets';
|
||||
```
|
||||
|
||||
23. 相对路径依赖
|
||||
|
||||
```typescript
|
||||
import { } from './components';
|
||||
```
|
||||
|
||||
##### 二、TS类型声明
|
||||
|
||||
```typescript
|
||||
interface Props {
|
||||
/**姓名 */
|
||||
name: string;
|
||||
/**年龄 */
|
||||
age?: number;
|
||||
}
|
||||
interface Emits {
|
||||
/**
|
||||
* 删除事件
|
||||
* @param id - 删除项的id
|
||||
*/
|
||||
(e: 'delete', id: number): void;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
##### 三、defineProps、defineEmits、withDefaults
|
||||
|
||||
1. 定义属性,如:
|
||||
|
||||
```typescript
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
age: 24
|
||||
});
|
||||
```
|
||||
|
||||
其中name是必须的属性,age是可选属性,通过withDefaults添加默认值
|
||||
|
||||
2. 定义emit事件
|
||||
|
||||
```typescript
|
||||
const emit = defineEmits<Emits>();
|
||||
```
|
||||
|
||||
##### 四、响应式use函数
|
||||
|
||||
有些use函数需要传入响应式的变量参数时,则书写在声明的变量下面。
|
||||
|
||||
```typescript
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
```
|
||||
|
||||
```typescript
|
||||
/**dom引用 */
|
||||
const domRef = ref<HTMLElement | null>(null);
|
||||
const { height: domRefHeight } = useElementSize(domRef); //获取domRef的响应式高度
|
||||
```
|
||||
|
||||
|
||||
|
||||
##### 五、变量、函数声明
|
||||
|
||||
##### 六、vue生命周期函数、nextTick执行
|
||||
|
||||
##### 七、defineExpose
|
72
doc/命名规范.md
72
doc/命名规范.md
@@ -1,72 +0,0 @@
|
||||
### 命名法:
|
||||
|
||||
#### 1.驼峰命名法(小驼峰)
|
||||
|
||||
**getUser**
|
||||
|
||||
#### 2.帕斯卡命名法(大驼峰)
|
||||
|
||||
**GlobalHeader**
|
||||
|
||||
#### 3.短横线命名法
|
||||
|
||||
**user-center**
|
||||
|
||||
#### 4.下划线命名法
|
||||
|
||||
**MAX_LENGTH**
|
||||
|
||||
### 文件、文件夹命名:
|
||||
|
||||
1. 文件夹作为**路由页面**时用小写字母,包含多个单词时,单词之间建议使用半角的连词线 ( - ) 分隔, 即**短横线命名法**,此时vue文件为**index.vue**。
|
||||
2. 文件夹作为**vue组件**时用**大写驼峰命名法**。
|
||||
3. 文件作为**vue组件**时用**大写驼峰命名法**。
|
||||
4. 文件作为**use函数**时用**小驼峰命名法**。
|
||||
5. 其余文件用**短横线命名法**。
|
||||
|
||||
### 变量命名:
|
||||
#### 命名方式 : 小驼峰式命名方法
|
||||
**命名规范 : 类型+对象描述的方式,如果没有明确的类型,就可以使前缀为名词**
|
||||
|
||||
动词 | 含义 | 返回值
|
||||
---|---|---
|
||||
can | 判断是否可执行某个动作 | 函数返回一个布尔值。true:可执行;false:不可执行。
|
||||
has | 判断是否含有某个值 | 函数返回一个布尔值。true:含有此值;false:不含有此值。
|
||||
is | 判断是否为某个值 | 函数返回一个布尔值。true:为某个值;false:不为某个值。
|
||||
get | 获取某个值 | 函数返回一个非布尔值。
|
||||
set | 设置某个值 | 无返回值、返回是否设置成功或者返回链式对象。
|
||||
|
||||
```javascript
|
||||
/** 是否可读 */
|
||||
function canRead(){
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 获取姓名 */
|
||||
function getName(){
|
||||
return this.name;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### 常量
|
||||
#### 命名方法 : 使用大写字母和下划线来组合命名,下划线用以分割单词。
|
||||
```javascript
|
||||
const MAX_COUNT = 10;
|
||||
const URL = 'http://www.baidu.com';
|
||||
```
|
||||
|
||||
### TS类型接口interface和type
|
||||
|
||||
##### 命名方法:大写驼峰
|
||||
|
||||
```typescript
|
||||
interface PersonInfo {
|
||||
/**姓名 */
|
||||
name: string;
|
||||
/**性别 '0':男; '1': 女; '2': 未知 */
|
||||
gender: '0' | '1' | '2';
|
||||
/**年龄 */
|
||||
age: 25;
|
||||
}
|
||||
```
|
101
doc/目录.md
101
doc/目录.md
@@ -1,101 +0,0 @@
|
||||
|
||||
## 目录规范
|
||||
|
||||
```javascript
|
||||
qitan-pc
|
||||
├── build //vite构建相关配置和插件
|
||||
│ ├── define //定义的全局常量,通过vite构建时注入
|
||||
│ ├── env //.env环境文件内容加载插件
|
||||
│ └── plugins //构建插件
|
||||
│ ├── html.ts //html插件(注入变量,压缩代码等)
|
||||
│ ├── iconify.ts //iconify图标插件
|
||||
│ ├── visualizer.ts //构建的依赖大小占比分析插件
|
||||
│ ├── vue.ts //vue相关vite插件
|
||||
│ └── windicss.ts //css框架插件
|
||||
├── doc //项目相关说明文档
|
||||
├── public //公共目录
|
||||
│ ├── resource //资源文件夹(不会被打包)
|
||||
│ └── favicon.ico //网站标签图标
|
||||
├── src
|
||||
│ ├── assets //静态资源
|
||||
│ ├── components //全局组件
|
||||
│ │ ├── business //业务相关组件
|
||||
│ │ ├── common //公共组件
|
||||
│ │ └── custom //自定义组件
|
||||
│ ├── context //全局上下文(通过provide和inject实现)
|
||||
│ │ ├── app //从app.vue注入的上下文
|
||||
│ │ └── part //局部组件注入的上下文
|
||||
│ ├── enum //TS枚举
|
||||
│ │ ├── animate.ts //动画枚举
|
||||
│ │ ├── business.ts //业务相关枚举
|
||||
│ │ ├── common.ts //通用枚举
|
||||
│ │ ├── route.ts //路由相关枚举
|
||||
│ │ ├── storage.ts //存储相关枚举
|
||||
│ │ └── theme.ts //系统主题配置相关枚举
|
||||
│ ├── hooks //组合式的钩子函数hooks
|
||||
│ │ ├── business //业务相关hooks
|
||||
│ │ └── common //通用hooks
|
||||
│ ├── interface //TS类型接口
|
||||
│ │ ├── business.ts //业务相关类型接口
|
||||
│ │ ├── common.ts //通用类型接口
|
||||
│ │ └── theme.ts //系统主题配置相关类型接口
|
||||
│ ├── layouts //布局组件
|
||||
│ │ ├── BasicLayout //基本布局(包含全局头部、侧边栏、底部等公共部分)
|
||||
│ │ ├── BlankLayout //空白布局组件(单个页面)
|
||||
│ │ └── RouterViewLayout //路由组件(用于多级路由之间的桥接)
|
||||
│ ├── plugins //插件
|
||||
│ │ └── dark-mode.ts //windicss暗黑模式插件
|
||||
│ ├── router //vue路由
|
||||
│ │ ├── modules //路由页面(按模块划分)
|
||||
│ │ ├── permission //路由权限(路由守卫)
|
||||
│ │ ├── routes //声明的路由
|
||||
│ │ └── setup //路由挂载函数
|
||||
│ ├── service //网络请求
|
||||
│ │ ├── api //接口api
|
||||
│ │ ├── middleware //请求结果的处理中间件
|
||||
│ │ └── request //封装的请求函数
|
||||
│ ├── settings //项目初始配置
|
||||
│ │ └── theme.ts //项目主题初始配置
|
||||
│ ├── store //状态管理
|
||||
│ │ └── modules //状态管理划分的模块
|
||||
│ ├── styles //样式
|
||||
│ │ ├── css //css
|
||||
│ │ └── scss //scss
|
||||
│ ├── typings //TS类型声明文件(*.d.ts)
|
||||
│ ├── utils //全局工具函数
|
||||
│ │ ├── auth //用户鉴权
|
||||
│ │ ├── common //通用工具函数
|
||||
│ │ ├── package //npm依赖
|
||||
│ │ ├── router //路由
|
||||
│ │ ├── request //请求工具函数
|
||||
│ │ └── storage //存储
|
||||
│ ├── views //页面
|
||||
│ │ ├── about
|
||||
│ │ ├── component
|
||||
│ │ ├── dashboard
|
||||
│ │ ├── document
|
||||
│ │ ├── multi-menu
|
||||
│ │ └── system //系统内置页面:登录、异常页等
|
||||
│ ├── App.vue //vue文件入口
|
||||
│ ├── AppProvider.vue //配置naive UI的vue文件(国际化,loadingBar、message等组件)
|
||||
│ └── main.ts //项目入口ts文件
|
||||
├── .cz-config.js //git cz提交配置
|
||||
├── .editorconfig //统一编辑器配置
|
||||
├── .env //环境文件
|
||||
├── .env.development //环境文件(开发模式)
|
||||
├── .env.production //环境文件(生产模式)
|
||||
├── .env.staging //环境文件(自定义staging模式)
|
||||
├── .eslintignore //忽略eslint检查的配置文件
|
||||
├── .eslintrc.js //eslint配置文件
|
||||
├── .gitignore //忽略git提交的配置文件
|
||||
├── .husky //git commit提交钩子,提交前检查代码格式和提交commit内容的格式
|
||||
├── .prettierrc.js //prettier代码格式插件配置
|
||||
├── commitlint.config.js //commitlint提交规范插件配置
|
||||
├── index.html
|
||||
├── package.json //npm依赖描述文件
|
||||
├── pnpm-lock.yaml //npm包管理器pnpm依赖锁定文件
|
||||
├── README.md //项目介绍文档
|
||||
├── tsconfig.json //TS配置
|
||||
├── vite.config.ts //vite配置
|
||||
└── windi.config.ts //windicss框架配置
|
||||
```
|
12
package.json
12
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "soybean-admin",
|
||||
"version": "0.0.2",
|
||||
"version": "0.0.4",
|
||||
"author": {
|
||||
"name": "Soybean",
|
||||
"email": "honghuangdc@gmail.com",
|
||||
@@ -32,13 +32,13 @@
|
||||
"dependencies": {
|
||||
"@antv/g2plot": "^2.3.40",
|
||||
"@better-scroll/core": "^2.4.2",
|
||||
"@vueuse/core": "^6.9.2",
|
||||
"@vueuse/core": "^7.0.0",
|
||||
"axios": "^0.24.0",
|
||||
"chroma-js": "^2.1.2",
|
||||
"clipboard": "^2.0.8",
|
||||
"dayjs": "^1.10.7",
|
||||
"form-data": "^4.0.0",
|
||||
"naive-ui": "^2.20.3",
|
||||
"naive-ui": "^2.21.0",
|
||||
"pinia": "^2.0.4",
|
||||
"print-js": "^1.6.0",
|
||||
"qs": "^6.10.1",
|
||||
@@ -68,14 +68,14 @@
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"cz-customizable": "^6.3.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"eslint": "^8.2.0",
|
||||
"eslint": "^8.3.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-import": "^2.25.3",
|
||||
"eslint-plugin-prettier": "^4.0.0",
|
||||
"eslint-plugin-vue": "^8.1.1",
|
||||
"husky": "^7.0.4",
|
||||
"lint-staged": "^12.0.3",
|
||||
"lint-staged": "^12.1.0",
|
||||
"patch-package": "^6.4.7",
|
||||
"postinstall-postinstall": "^2.1.0",
|
||||
"prettier": "^2.4.1",
|
||||
@@ -87,7 +87,7 @@
|
||||
"vite": "~2.5.10",
|
||||
"vite-plugin-html": "^2.1.1",
|
||||
"vite-plugin-windicss": "^1.5.1",
|
||||
"vue-tsc": "~0.28.10",
|
||||
"vue-tsc": "^0.29.6",
|
||||
"vueuc": "^0.4.15",
|
||||
"windicss": "^3.2.1"
|
||||
},
|
||||
|
904
pnpm-lock.yaml
generated
904
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
26
src/assets/svg/common/logo.svg
Normal file
26
src/assets/svg/common/logo.svg
Normal file
@@ -0,0 +1,26 @@
|
||||
<?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>
|
After Width: | Height: | Size: 2.1 KiB |
@@ -24,6 +24,8 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
});
|
||||
|
||||
const COLOR_WHITE = '#ffffff';
|
||||
const stopColor = computed(() => mixColor(COLOR_WHITE, props.themeColor, 0.7));
|
||||
const stopColor = computed(() => {
|
||||
return mixColor(COLOR_WHITE, props.themeColor, 0.7);
|
||||
});
|
||||
</script>
|
||||
<style scoped></style>
|
||||
|
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div ref="scrollbar" class="wh-full text-left">
|
||||
<div ref="scrollbar" class="h-full text-left">
|
||||
<div ref="scrollbarContent" class="inline-block" :class="{ 'h-full': !isScrollY }">
|
||||
<slot></slot>
|
||||
</div>
|
||||
@@ -14,17 +14,15 @@ import { useElementSize } from '@vueuse/core';
|
||||
|
||||
interface Props {
|
||||
/** better-scroll的配置: https://better-scroll.github.io/docs/zh-CN/guide/base-scroll-options.html */
|
||||
options?: Options;
|
||||
options: Options;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
options: undefined
|
||||
});
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const scrollbar = ref<HTMLElement | null>(null);
|
||||
const bsInstance = ref<BScroll | null>(null);
|
||||
const scrollbarContent = ref<HTMLElement | null>(null);
|
||||
const isScrollY = computed(() => Boolean(props.options?.scrollY));
|
||||
const isScrollY = computed(() => Boolean(props.options.scrollY));
|
||||
|
||||
function initBetterScroll() {
|
||||
bsInstance.value = new BScroll(scrollbar.value!, props.options);
|
||||
@@ -41,5 +39,7 @@ watch([() => width.value, () => height.value], () => {
|
||||
onMounted(() => {
|
||||
initBetterScroll();
|
||||
});
|
||||
|
||||
defineExpose({ bsInstance });
|
||||
</script>
|
||||
<style scoped></style>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="relative flex-y-center h-34px px-24px cursor-pointer -mr-18px"
|
||||
:class="{ 'z-10': isActive, 'z-9': isHover }"
|
||||
class="relative flex-y-center h-34px px-24px cursor-pointer"
|
||||
:class="{ '-mr-18px': !isLast, 'z-10': isActive, 'z-9': isHover }"
|
||||
@mouseenter="setTrue"
|
||||
@mouseleave="setFalse"
|
||||
>
|
||||
@@ -39,14 +39,18 @@ interface Props {
|
||||
closable?: boolean;
|
||||
/** 暗黑模式 */
|
||||
darkMode?: boolean;
|
||||
/** 是否是最后一个 */
|
||||
isLast: boolean;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
isActive: false,
|
||||
primaryColor: '#409EFF',
|
||||
closable: true,
|
||||
darkMode: false
|
||||
darkMode: false,
|
||||
isLast: false
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
/** 点击关闭图标 */
|
||||
(e: 'close'): void;
|
||||
|
@@ -6,8 +6,8 @@
|
||||
@mouseleave="setFalse"
|
||||
>
|
||||
<transition name="transition-opacity">
|
||||
<icon-carbon-close-filled v-if="isHover" key="hover" class="absolute" />
|
||||
<icon-carbon-close v-else key="unhover" class="absolute" />
|
||||
<icon-mdi:close-circle v-if="isHover" key="hover" class="absolute" />
|
||||
<icon-mdi:close v-else key="unhover" class="absolute" />
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
40
src/components/custom/ThemeSwitch/index.vue
Normal file
40
src/components/custom/ThemeSwitch/index.vue
Normal file
@@ -0,0 +1,40 @@
|
||||
<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>
|
@@ -5,5 +5,6 @@ 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';
|
||||
|
||||
export { CountTo, IconClose, ButtonTab, ChromeTab, BetterScroll, WebSiteLink, GithubLink };
|
||||
export { CountTo, IconClose, ButtonTab, ChromeTab, BetterScroll, WebSiteLink, GithubLink, ThemeSwitch };
|
||||
|
@@ -12,6 +12,24 @@ export function useDarkMode() {
|
||||
/** 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,
|
||||
@@ -22,6 +40,18 @@ export function useDarkMode() {
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
// 监听主题的暗黑模式
|
||||
watch(
|
||||
() => theme.darkMode,
|
||||
newValue => {
|
||||
if (newValue) {
|
||||
addDarkClass();
|
||||
} else {
|
||||
removeDarkClass();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
return {
|
||||
naiveTheme
|
||||
|
2
src/config/common/index.ts
Normal file
2
src/config/common/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './service';
|
||||
export * from './map-sdk';
|
@@ -1,6 +1,7 @@
|
||||
/** 百度地图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';
|
||||
|
39
src/config/common/service.ts
Normal file
39
src/config/common/service.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/** 请求超时时间 */
|
||||
export const REQUEST_TIMEOUT = 60 * 1000;
|
||||
|
||||
/** 错误信息的显示时间 */
|
||||
export const ERROR_MSG_DURATION = 3 * 1000;
|
||||
|
||||
/** 兜底的请求错误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 = '网络不可用~';
|
||||
|
||||
/** 请求不成功各种状态的错误 */
|
||||
export const ERROR_STATUS = {
|
||||
400: '400: 请求出现语法错误',
|
||||
401: '401: 用户未授权~',
|
||||
403: '403: 服务器拒绝访问~',
|
||||
404: '404: 请求的资源不存在~',
|
||||
405: '405: 请求方法未允许~',
|
||||
408: '408: 网络请求超时~',
|
||||
500: '500: 服务器内部错误~',
|
||||
501: '501: 服务器未实现请求功能~',
|
||||
502: '502: 错误网关~',
|
||||
503: '503: 服务不可用~',
|
||||
504: '504: 网关超时~',
|
||||
505: '505: http版本不支持该请求~'
|
||||
};
|
||||
|
||||
/** 不弹出错误信息的code */
|
||||
export const NO_ERROR_MSG_CODE: (string | number)[] = [];
|
1
src/config/index.ts
Normal file
1
src/config/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './common';
|
0
src/directives/index.ts
Normal file
0
src/directives/index.ts
Normal file
@@ -3,20 +3,22 @@ import { ref } from 'vue';
|
||||
export default function useBoolean(initValue: boolean = false) {
|
||||
const bool = ref(initValue);
|
||||
|
||||
function setBool(value: boolean) {
|
||||
bool.value = value;
|
||||
}
|
||||
function setTrue() {
|
||||
bool.value = true;
|
||||
setBool(true);
|
||||
}
|
||||
|
||||
function setFalse() {
|
||||
bool.value = false;
|
||||
setBool(false);
|
||||
}
|
||||
|
||||
function toggle() {
|
||||
bool.value = !bool.value;
|
||||
setBool(!bool.value);
|
||||
}
|
||||
|
||||
return {
|
||||
bool,
|
||||
setBool,
|
||||
setTrue,
|
||||
setFalse,
|
||||
toggle
|
||||
|
15
src/interface/business/demo.ts
Normal file
15
src/interface/business/demo.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/** 数据字典 */
|
||||
export interface Dictionary {
|
||||
/** 字典名字 */
|
||||
label: string;
|
||||
/** 要素名字(一级指标) */
|
||||
charactorLabel: string;
|
||||
/** 要素下的指标key(二级指标) */
|
||||
indicatorKey: string;
|
||||
/** 要素下的指标名字(二级指标) */
|
||||
indicatorLabel: string;
|
||||
/** 备注 */
|
||||
remark: string;
|
||||
/** 指标公式 */
|
||||
formula: string;
|
||||
}
|
@@ -1 +1,3 @@
|
||||
export * from './auth';
|
||||
export * from './demo';
|
||||
export * from './website';
|
||||
|
18
src/interface/business/website.ts
Normal file
18
src/interface/business/website.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
/** 网址导航 */
|
||||
export interface Website {
|
||||
/** 网址名称 */
|
||||
title: string;
|
||||
/** 网址简称 */
|
||||
abbr: string;
|
||||
/** 网址图标(网络地址,形状为正方形) */
|
||||
logo: string;
|
||||
/** 描述 */
|
||||
desc: string;
|
||||
}
|
||||
|
||||
/** 网址导航 分类 */
|
||||
export interface WebsiteCategory {
|
||||
/** 分类名称 */
|
||||
title: string;
|
||||
children: WebsiteCategory[] | Website[];
|
||||
}
|
15
src/interface/common/api.ts
Normal file
15
src/interface/common/api.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/** 数据字典 */
|
||||
export interface ResponseDictionary {
|
||||
/** 字典名字 */
|
||||
modelName: string;
|
||||
/** 要素名字(一级指标) */
|
||||
modelCharactorName: string;
|
||||
/** 要素下的指标key(二级指标) */
|
||||
modelIndicator: string;
|
||||
/** 要素下的指标名字(二级指标) */
|
||||
modelIndicatorName: string;
|
||||
/** 备注 */
|
||||
remarks: string;
|
||||
/** 指标公式 */
|
||||
formula: string;
|
||||
}
|
7
src/interface/common/expose.ts
Normal file
7
src/interface/common/expose.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { Ref } from 'vue';
|
||||
import BScroll from '@better-scroll/core';
|
||||
|
||||
/** BetterScroll暴露出的数据的类型 */
|
||||
export interface ExposeBetterScroll {
|
||||
bsInstance: Ref<BScroll>;
|
||||
}
|
@@ -1,2 +1,6 @@
|
||||
export * from './theme';
|
||||
export * from './system';
|
||||
export * from './route';
|
||||
export * from './expose';
|
||||
export * from './service';
|
||||
export * from './api';
|
||||
|
@@ -55,4 +55,5 @@ export type RouteKey =
|
||||
| 'exception_403'
|
||||
| 'exception_404'
|
||||
| 'exception_500'
|
||||
| 'about';
|
||||
| 'about'
|
||||
| 'website';
|
||||
|
46
src/interface/common/service.ts
Normal file
46
src/interface/common/service.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
/**
|
||||
* 请求服务的错误类型:
|
||||
* - axios: axios错误:网络错误, 请求超时, 默认的兜底错误
|
||||
* - http: 请求成功,响应的状态码非200的错误
|
||||
* - backend: 请求成功,响应的状态码为200,由后端定义的业务错误
|
||||
*/
|
||||
export type RequestServiceErrorType = 'axios' | 'http' | 'backend';
|
||||
|
||||
/** 请求服务的错误 */
|
||||
export interface RequestServiceError {
|
||||
/** 请求服务的错误类型 */
|
||||
type: RequestServiceErrorType;
|
||||
/** 错误码 */
|
||||
code: string | number;
|
||||
/** 错误信息 */
|
||||
msg: string;
|
||||
}
|
||||
|
||||
/** 后端接口返回的类型结构 */
|
||||
export interface BackendServiceResult {
|
||||
/** 状态码 */
|
||||
code: number;
|
||||
/** 接口数据 */
|
||||
data: any;
|
||||
/** 接口消息 */
|
||||
message: string;
|
||||
}
|
||||
|
||||
/** 自定义的请求成功结果 */
|
||||
export interface CustomSuccessRequestResult<ResponseData> {
|
||||
/** 请求错误 */
|
||||
error: null;
|
||||
/** 请求数据 */
|
||||
data: ResponseData;
|
||||
}
|
||||
|
||||
/** 自定义的请求失败结果 */
|
||||
export interface CustomFailRequestResult {
|
||||
/** 请求错误 */
|
||||
error: RequestServiceError;
|
||||
/** 请求数据 */
|
||||
data: null;
|
||||
}
|
||||
|
||||
/** 自定义的请求结果 */
|
||||
export type CustomRequestResult<ResponseData> = CustomSuccessRequestResult<ResponseData> | CustomFailRequestResult;
|
@@ -1,3 +1,2 @@
|
||||
export * from './common';
|
||||
export * from './business';
|
||||
export * from './theme';
|
||||
|
@@ -16,7 +16,7 @@
|
||||
import { NLayout, NLayoutContent, NLayoutHeader } from 'naive-ui';
|
||||
import { useThemeStore } from '@/store';
|
||||
import { useLayoutConfig } from '@/composables';
|
||||
import { GlobalHeader, GlobalContent, GlobalFooter, GlobalTab, SpacePlaceholder } from '../common';
|
||||
import { GlobalHeader, GlobalContent, GlobalFooter, GlobalTab, SpacePlaceholder } from '@/layouts/common';
|
||||
|
||||
const theme = useThemeStore();
|
||||
const { headerInverted, headerPosition, scrollbarContentStyle, scrollbar } = useLayoutConfig();
|
||||
|
@@ -39,7 +39,7 @@
|
||||
import { NLayout, NLayoutContent, NLayoutSider, NLayoutHeader, NScrollbar } from 'naive-ui';
|
||||
import { useThemeStore, useAppStore } from '@/store';
|
||||
import { useLayoutConfig } from '@/composables';
|
||||
import { GlobalHeader, GlobalContent, GlobalFooter, GlobalTab, GlobalMenu, SpacePlaceholder } from '../common';
|
||||
import { GlobalHeader, GlobalContent, GlobalFooter, GlobalTab, GlobalMenu, SpacePlaceholder } from '@/layouts/common';
|
||||
|
||||
const theme = useThemeStore();
|
||||
const app = useAppStore();
|
||||
|
@@ -45,7 +45,7 @@ import {
|
||||
SpacePlaceholder,
|
||||
GlobalLogo,
|
||||
GlobalMenu
|
||||
} from '../common';
|
||||
} from '@/layouts/common';
|
||||
|
||||
const theme = useThemeStore();
|
||||
const app = useAppStore();
|
||||
|
@@ -21,7 +21,7 @@
|
||||
import { NLayout, NLayoutContent, NLayoutHeader } from 'naive-ui';
|
||||
import { useThemeStore } from '@/store';
|
||||
import { useLayoutConfig } from '@/composables';
|
||||
import { MixSider, GlobalHeader, GlobalContent, GlobalFooter, GlobalTab, SpacePlaceholder } from '../common';
|
||||
import { MixSider, GlobalHeader, GlobalContent, GlobalFooter, GlobalTab, SpacePlaceholder } from '@/layouts/common';
|
||||
|
||||
const theme = useThemeStore();
|
||||
const { headerInverted, headerPosition, globalSiderClassAndStyle, scrollbarContentStyle, scrollbar } =
|
||||
|
@@ -1,40 +0,0 @@
|
||||
<template>
|
||||
<div class="multi-tab flex-center w-full pl-16px" :style="{ height: multiTabHeight }">
|
||||
<div class="flex-1-hidden h-full">
|
||||
<better-scroll :options="{ scrollX: true, scrollY: false, click: isMobile }">
|
||||
<multi-tab />
|
||||
</better-scroll>
|
||||
</div>
|
||||
<reload-button />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useAppStore } from '@/store';
|
||||
import { useLayoutConfig, routeFullPathWatcher, useIsMobile } from '@/composables';
|
||||
import { BetterScroll } from '@/components';
|
||||
import { MultiTab, ReloadButton } from './components';
|
||||
|
||||
const route = useRoute();
|
||||
const { initMultiTab, addMultiTab, setActiveMultiTab } = useAppStore();
|
||||
const { multiTabHeight } = useLayoutConfig();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
function init() {
|
||||
initMultiTab();
|
||||
}
|
||||
|
||||
routeFullPathWatcher(fullPath => {
|
||||
addMultiTab(route);
|
||||
setActiveMultiTab(fullPath);
|
||||
});
|
||||
|
||||
// 初始化
|
||||
init();
|
||||
</script>
|
||||
<style scoped>
|
||||
.multi-tab {
|
||||
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
|
||||
}
|
||||
</style>
|
@@ -4,4 +4,3 @@ import HorizontalLayout from './HorizontalLayout/index.vue';
|
||||
import HorizontalMixLayout from './HorizontalMixLayout/index.vue';
|
||||
|
||||
export { VerticalLayout, VerticalMixLayout, HorizontalLayout, HorizontalMixLayout };
|
||||
export * from './common';
|
||||
|
@@ -7,7 +7,8 @@
|
||||
import type { Component } from 'vue';
|
||||
import { useThemeStore } from '@/store';
|
||||
import type { NavMode } from '@/interface';
|
||||
import { VerticalLayout, VerticalMixLayout, HorizontalLayout, HorizontalMixLayout, SettingDrawer } from './components';
|
||||
import { VerticalLayout, VerticalMixLayout, HorizontalLayout, HorizontalMixLayout } from './components';
|
||||
import { SettingDrawer } from '../common';
|
||||
|
||||
type LayoutComponent = {
|
||||
[key in NavMode]: Component;
|
||||
|
@@ -1,31 +1,14 @@
|
||||
<template>
|
||||
<n-scrollbar ref="scrollbar" class="h-full" :content-class="routeProps.fullPage ? 'h-full' : ''">
|
||||
<div class="inline-block wh-full bg-[#f6f9f8]">
|
||||
<router-view v-slot="{ Component, route: itemRoute }">
|
||||
<transition :name="theme.pageAnimateType" mode="out-in" appear>
|
||||
<keep-alive :include="cacheRoutes">
|
||||
<component
|
||||
:is="Component"
|
||||
v-if="app.reloadFlag"
|
||||
:key="itemRoute.fullPath"
|
||||
:class="{ 'min-h-100vh': !routeProps.fullPage }"
|
||||
/>
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
<n-scrollbar ref="scrollbar" class="h-full" :content-style="scrollbarContentStyle">
|
||||
<global-content :show-padding="false" />
|
||||
</n-scrollbar>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { NScrollbar } from 'naive-ui';
|
||||
import { useThemeStore, useAppStore } from '@/store';
|
||||
import { cacheRoutes } from '@/router';
|
||||
import { useRouteProps } from '@/composables';
|
||||
import { useLayoutConfig } from '@/composables';
|
||||
import { GlobalContent } from '../common';
|
||||
|
||||
const theme = useThemeStore();
|
||||
const app = useAppStore();
|
||||
|
||||
const routeProps = useRouteProps();
|
||||
const { scrollbarContentStyle } = useLayoutConfig();
|
||||
</script>
|
||||
<style scoped></style>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="flex-1 flex-col-stretch bg-[#f6f9f8] dark:bg-deep-dark p-16px transition-all duration-300 ease-in-out"
|
||||
:class="{ 'overflow-hidden': routeProps.fullPage }"
|
||||
class="flex-1 flex-col-stretch bg-[#f6f9f8] dark:bg-deep-dark transition-all duration-300 ease-in-out"
|
||||
:class="{ 'overflow-hidden': routeProps.fullPage, 'p-16px': showPadding }"
|
||||
>
|
||||
<router-view v-slot="{ Component, route }">
|
||||
<transition :name="theme.pageAnimateType" mode="out-in" appear>
|
||||
@@ -18,6 +18,15 @@ import { cacheRoutes } from '@/router';
|
||||
import { useAppStore, useThemeStore } from '@/store';
|
||||
import { useRouteProps } from '@/composables';
|
||||
|
||||
interface Props {
|
||||
/** 显示padding */
|
||||
showPadding?: boolean;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
showPadding: true
|
||||
});
|
||||
|
||||
const theme = useThemeStore();
|
||||
const app = useAppStore();
|
||||
const routeProps = useRouteProps();
|
@@ -1,12 +1,13 @@
|
||||
<template>
|
||||
<div v-if="theme.multiTabStyle.mode === 'chrome'" class="flex items-end h-full">
|
||||
<div v-if="theme.multiTabStyle.mode === 'chrome'" ref="tabRef" class="flex items-end h-full">
|
||||
<chrome-tab
|
||||
v-for="item in app.multiTab.routes"
|
||||
v-for="(item, index) in app.multiTab.routes"
|
||||
:key="item.path"
|
||||
:is-active="app.multiTab.activeRoute === item.fullPath"
|
||||
:primary-color="theme.themeColor"
|
||||
:closable="item.name !== ROUTE_HOME.name"
|
||||
:dark-mode="theme.darkMode"
|
||||
:is-last="index === app.multiTab.routes.length - 1"
|
||||
@click="handleClickTab(item.fullPath)"
|
||||
@close="removeMultiTab(item.fullPath)"
|
||||
@contextmenu="handleContextMenu($event, item.fullPath)"
|
||||
@@ -14,7 +15,7 @@
|
||||
{{ item.meta?.title }}
|
||||
</chrome-tab>
|
||||
</div>
|
||||
<div v-if="theme.multiTabStyle.mode === 'button'" class="flex-y-center h-full">
|
||||
<div v-if="theme.multiTabStyle.mode === 'button'" ref="tabRef" class="flex-y-center h-full">
|
||||
<button-tab
|
||||
v-for="item in app.multiTab.routes"
|
||||
:key="item.path"
|
||||
@@ -39,7 +40,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, nextTick } from 'vue';
|
||||
import { ref, reactive, nextTick, watch } from 'vue';
|
||||
import { useEventListener } from '@vueuse/core';
|
||||
import { useThemeStore, useAppStore } from '@/store';
|
||||
import { ROUTE_HOME } from '@/router';
|
||||
@@ -48,6 +49,12 @@ import { useBoolean } from '@/hooks';
|
||||
import { setTabRouteStorage } from '@/utils';
|
||||
import { ContextMenu } from './components';
|
||||
|
||||
interface Emits {
|
||||
(e: 'scroll', clientX: number): void;
|
||||
}
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
const theme = useThemeStore();
|
||||
const app = useAppStore();
|
||||
const { removeMultiTab, handleClickTab } = useAppStore();
|
||||
@@ -62,6 +69,22 @@ function setDropdownConfig(x: number, y: number, currentPath: string) {
|
||||
Object.assign(dropdownConfig, { x, y, currentPath });
|
||||
}
|
||||
|
||||
// 获取当前激活的tab的clientX
|
||||
const tabRef = ref<HTMLElement | null>(null);
|
||||
async function getActiveTabClientX() {
|
||||
await nextTick();
|
||||
const index = app.activeMultiTabIndex;
|
||||
if (tabRef.value) {
|
||||
const activeTabElement = tabRef.value.children[index];
|
||||
const { x, width } = activeTabElement.getBoundingClientRect();
|
||||
const clientX = x + width / 2;
|
||||
setTimeout(() => {
|
||||
emit('scroll', clientX);
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
|
||||
// 右键菜单
|
||||
function handleContextMenu(e: MouseEvent, fullPath: string) {
|
||||
e.preventDefault();
|
||||
const { clientX, clientY } = e;
|
||||
@@ -76,5 +99,15 @@ function handleContextMenu(e: MouseEvent, fullPath: string) {
|
||||
useEventListener(window, 'beforeunload', () => {
|
||||
setTabRouteStorage(app.multiTab.routes);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => app.activeMultiTabIndex,
|
||||
() => {
|
||||
getActiveTabClientX();
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style scoped></style>
|
59
src/layouts/common/GlobalTab/index.vue
Normal file
59
src/layouts/common/GlobalTab/index.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="multi-tab flex-center w-full pl-16px" :style="{ height: multiTabHeight }">
|
||||
<div ref="bsWrapperRef" class="flex-1-hidden h-full">
|
||||
<better-scroll ref="bsScroll" :options="{ scrollX: true, scrollY: false, click: isMobile }">
|
||||
<multi-tab @scroll="handleScroll" />
|
||||
</better-scroll>
|
||||
</div>
|
||||
<reload-button />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useElementBounding } from '@vueuse/core';
|
||||
import { useAppStore } from '@/store';
|
||||
import { useLayoutConfig, routeFullPathWatcher, useIsMobile } from '@/composables';
|
||||
import { BetterScroll } from '@/components';
|
||||
import type { ExposeBetterScroll } from '@/interface';
|
||||
import { MultiTab, ReloadButton } from './components';
|
||||
|
||||
const route = useRoute();
|
||||
const { initMultiTab, addMultiTab, setActiveMultiTab } = useAppStore();
|
||||
const { multiTabHeight } = useLayoutConfig();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const bsWrapperRef = ref<HTMLElement | null>(null);
|
||||
const { width: bsWrapperWidth, left: bsWrapperLeft } = useElementBounding(bsWrapperRef);
|
||||
|
||||
const bsScroll = ref<ExposeBetterScroll | null>(null);
|
||||
|
||||
function handleScroll(clientX: number) {
|
||||
const currentX = clientX - bsWrapperLeft.value;
|
||||
const deltaX = currentX - bsWrapperWidth.value / 2;
|
||||
if (bsScroll.value) {
|
||||
const { maxScrollX, x: leftX } = bsScroll.value.bsInstance;
|
||||
const rightX = maxScrollX - leftX;
|
||||
const update = deltaX > 0 ? Math.max(-deltaX, rightX) : Math.min(-deltaX, -leftX);
|
||||
bsScroll.value?.bsInstance.scrollBy(update, 0, 300);
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
initMultiTab();
|
||||
}
|
||||
|
||||
routeFullPathWatcher(fullPath => {
|
||||
addMultiTab(route);
|
||||
setActiveMultiTab(fullPath);
|
||||
});
|
||||
|
||||
// 初始化
|
||||
init();
|
||||
</script>
|
||||
<style scoped>
|
||||
.multi-tab {
|
||||
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
|
||||
}
|
||||
</style>
|
@@ -3,13 +3,11 @@ import App from './App.vue';
|
||||
import AppProvider from './AppProvider.vue';
|
||||
import { setupStore } from './store';
|
||||
import { setupRouter } from './router';
|
||||
import { setupAssets, setupWindicssDarkMode } from './plugins';
|
||||
import { setupAssets } from './plugins';
|
||||
|
||||
function setupPlugins() {
|
||||
/** 引入静态资源 */
|
||||
setupAssets();
|
||||
// 配置windicss暗黑主题
|
||||
setupWindicssDarkMode();
|
||||
}
|
||||
|
||||
async function setupApp() {
|
||||
|
@@ -1,36 +0,0 @@
|
||||
import { watch } from 'vue';
|
||||
import { useThemeStore } from '@/store';
|
||||
|
||||
export default function setupWindicssDarkMode() {
|
||||
const theme = useThemeStore();
|
||||
|
||||
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(
|
||||
() => theme.darkMode,
|
||||
newValue => {
|
||||
if (newValue) {
|
||||
addDarkClass();
|
||||
} else {
|
||||
removeDarkClass();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
}
|
@@ -1,4 +1,3 @@
|
||||
import setupAssets from './assets';
|
||||
import setupWindicssDarkMode from './dark-mode';
|
||||
|
||||
export { setupAssets, setupWindicssDarkMode };
|
||||
export { setupAssets };
|
||||
|
@@ -258,6 +258,14 @@ const routeConstMap = new Map<RouteKey, RouteConst>([
|
||||
path: '/about',
|
||||
title: '关于'
|
||||
}
|
||||
],
|
||||
[
|
||||
'website',
|
||||
{
|
||||
name: 'website',
|
||||
path: '/website',
|
||||
title: '网址导航'
|
||||
}
|
||||
]
|
||||
]);
|
||||
|
||||
|
27
src/router/modules/website.ts
Normal file
27
src/router/modules/website.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import type { CustomRoute } from '@/interface';
|
||||
import { setSingleRoute } from '@/utils';
|
||||
import { BlankLayout } from '@/layouts';
|
||||
import Website from '@/views/website/index.vue';
|
||||
import { getRouteConst, routeName } from '../constant';
|
||||
|
||||
const { name, path, title } = getRouteConst('website');
|
||||
|
||||
const WEBSITE: CustomRoute = setSingleRoute({
|
||||
route: {
|
||||
name,
|
||||
path,
|
||||
component: Website,
|
||||
meta: {
|
||||
title,
|
||||
icon: 'codicon:remote-explorer',
|
||||
isNotMenu: true
|
||||
}
|
||||
},
|
||||
container: BlankLayout,
|
||||
meta: {
|
||||
order: 8
|
||||
},
|
||||
notFoundName: routeName('not-found')
|
||||
});
|
||||
|
||||
export default WEBSITE;
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user