Compare commits

..

23 Commits

Author SHA1 Message Date
Soybean
8b27fc8bb8 chore(release): 0.0.4 2021-11-25 22:44:19 +08:00
Soybean
738964a769 fix(projects): 添加西瓜视频实例在onUnMounted的销毁,多页签居中距离精确 2021-11-25 22:16:34 +08:00
Soybean
c429cd0293 fix(components): 修复多页签按钮风格的tab滚动问题 2021-11-25 22:02:28 +08:00
Soybean
20aa39f14e fix(components): 修复多页签Tab自动滚动问题 2021-11-25 21:50:20 +08:00
Soybean
93f9aa9584 fix(projects): 修复打包构建时图标错误 2021-11-25 01:18:51 +08:00
Soybean
8ce627a397 feat(components): 添加多页签Tab点击后自动往中间滚动 2021-11-25 00:50:55 +08:00
Soybean
1ffb75afce refactor(components): blankLayout引入GlobalContent 2021-11-24 23:54:10 +08:00
Soybean
32aa5ee75a feat(projects): 新增网址导航页面 2021-11-24 19:14:13 +08:00
Soybean
98a7d25cf8 docs(projects): update README.md 2021-11-24 12:02:10 +08:00
Soybean
1b3463d2e7 docs(projects): update README.md 2021-11-24 11:53:32 +08:00
Soybean
cff11d9175 fix(types): 添加dotEnv类型的非空判断 2021-11-24 00:51:33 +08:00
Soybean
54577f10fc docs(projects): update README.md 2021-11-24 00:46:58 +08:00
Soybean
6261156c5a refactor(projects): 细节完善 2021-11-23 18:08:56 +08:00
Soybean
f5a5f44a2b refactor(projects): 文件夹位置规范 2021-11-23 10:55:20 +08:00
Soybean
3fb7a5f709 Merge pull request #19 from yanbowe/main
feat(storage): local存储增加有效期
2021-11-23 10:15:37 +08:00
Yanbowen
e6c9b35ab4 feat(storage): local存储增加有效期 2021-11-23 09:57:59 +08:00
Soybean
0569666a8f refactor(projects): axios处理的请求结果去除网路状态 2021-11-23 09:52:23 +08:00
Soybean
e1dacdbc39 chore(release): 0.0.3 2021-11-23 09:28:21 +08:00
Soybean
03b398af2f refactor(projects): axios封装完成 2021-11-23 00:23:43 +08:00
Soybean
451c7547af refactor(projects): axios封装:文件夹规范,错误处理完善 2021-11-22 18:32:32 +08:00
Soybean
c81221efac build(deps): 升级依赖 2021-11-22 00:36:38 +08:00
Soybean
4e04a8f8ad build(deps): 升级依赖 2021-11-22 00:22:50 +08:00
Soybean
9f64321d73 refactor(projects): 请求函数重构初步 2021-11-22 00:14:12 +08:00
130 changed files with 1381 additions and 1446 deletions

View File

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

View File

@@ -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

View File

@@ -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

View File

@@ -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"
}
}

View File

@@ -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)

View File

@@ -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](https://raw.githubusercontent.com/honghuangdc/project-assets/main/img/license.svg)](LICENSE)
[![license](https://img.shields.io/badge/license-MIT-green.svg)](./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)
## 项目示例图
![](https://i.loli.net/2021/11/24/pIhTKP7fdCqbVHl.png)
![](https://i.loli.net/2021/11/24/gxRwsLnKi6IVp7C.png)
![](https://i.loli.net/2021/11/24/UmVfjSJbxH6iYc2.png)
![](https://i.loli.net/2021/11/24/Uot1bcfGXiF726T.png)
![](https://i.loli.net/2021/11/24/WzOIvlgJZaUtGm7.png)
### 使用 Gitpod
@@ -52,10 +48,6 @@ Soybean Admin 是一个基于 Vue3、Vite、Naive UI、TypeScript 的免费中
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](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
View File

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

View File

@@ -1,47 +0,0 @@
### 1.interface和type
##### interface和type使用优先级能用interface表示的类型就用interface。
### 2.请求函数
#### api接口
统一以 **fetch** 开头,例如:
```typescript
/**
* 获取用户信息
* @param id - 用户唯一标识id
*/
function fetchUserInfo(idstring) {
// ***
}
/**
* 删除列表项
* @param id - 列表id
*/
function fetchDeleteListItem(idstring) {
// ***
}
```
#### middleware中间件
统一以 **handle** 开头,例如
```typescript
/**接口返回的用户信息 */
interface ResponseUserInfo {
userId: string;
userName: string;
userAge: number;
}
/**
* 获取用户信息 中间件
@param data - 返回的用户信息
*/
function handleUserInfo(data: ResponseUserInfo): UserInfo {
// ***
}
```

View File

@@ -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>

View File

@@ -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" />`
*psIcon组件属性 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*

View File

@@ -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

View File

@@ -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;
}
```

View File

@@ -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框架配置
```

View File

@@ -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

File diff suppressed because it is too large Load Diff

View 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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;

View File

@@ -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>

View 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>

View File

@@ -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 };

View File

@@ -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

View File

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

View File

@@ -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';

View 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
View File

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

0
src/directives/index.ts Normal file
View File

View 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

View File

@@ -0,0 +1,15 @@
/** 数据字典 */
export interface Dictionary {
/** 字典名字 */
label: string;
/** 要素名字(一级指标) */
charactorLabel: string;
/** 要素下的指标key(二级指标) */
indicatorKey: string;
/** 要素下的指标名字(二级指标) */
indicatorLabel: string;
/** 备注 */
remark: string;
/** 指标公式 */
formula: string;
}

View File

@@ -1 +1,3 @@
export * from './auth';
export * from './demo';
export * from './website';

View File

@@ -0,0 +1,18 @@
/** 网址导航 */
export interface Website {
/** 网址名称 */
title: string;
/** 网址简称 */
abbr: string;
/** 网址图标(网络地址,形状为正方形) */
logo: string;
/** 描述 */
desc: string;
}
/** 网址导航 分类 */
export interface WebsiteCategory {
/** 分类名称 */
title: string;
children: WebsiteCategory[] | Website[];
}

View File

@@ -0,0 +1,15 @@
/** 数据字典 */
export interface ResponseDictionary {
/** 字典名字 */
modelName: string;
/** 要素名字(一级指标) */
modelCharactorName: string;
/** 要素下的指标key(二级指标) */
modelIndicator: string;
/** 要素下的指标名字(二级指标) */
modelIndicatorName: string;
/** 备注 */
remarks: string;
/** 指标公式 */
formula: string;
}

View File

@@ -0,0 +1,7 @@
import type { Ref } from 'vue';
import BScroll from '@better-scroll/core';
/** BetterScroll暴露出的数据的类型 */
export interface ExposeBetterScroll {
bsInstance: Ref<BScroll>;
}

View File

@@ -1,2 +1,6 @@
export * from './theme';
export * from './system';
export * from './route';
export * from './expose';
export * from './service';
export * from './api';

View File

@@ -55,4 +55,5 @@ export type RouteKey =
| 'exception_403'
| 'exception_404'
| 'exception_500'
| 'about';
| 'about'
| 'website';

View 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;

View File

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

View File

@@ -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();

View File

@@ -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();

View File

@@ -45,7 +45,7 @@ import {
SpacePlaceholder,
GlobalLogo,
GlobalMenu
} from '../common';
} from '@/layouts/common';
const theme = useThemeStore();
const app = useAppStore();

View File

@@ -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 } =

View File

@@ -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>

View File

@@ -4,4 +4,3 @@ import HorizontalLayout from './HorizontalLayout/index.vue';
import HorizontalMixLayout from './HorizontalMixLayout/index.vue';
export { VerticalLayout, VerticalMixLayout, HorizontalLayout, HorizontalMixLayout };
export * from './common';

View File

@@ -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;

View File

@@ -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>

View File

@@ -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();

View File

@@ -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 });
}
// tabclientX
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>

View 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>

View File

@@ -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() {

View File

@@ -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 }
);
}

View File

@@ -1,4 +1,3 @@
import setupAssets from './assets';
import setupWindicssDarkMode from './dark-mode';
export { setupAssets, setupWindicssDarkMode };
export { setupAssets };

View File

@@ -258,6 +258,14 @@ const routeConstMap = new Map<RouteKey, RouteConst>([
path: '/about',
title: '关于'
}
],
[
'website',
{
name: 'website',
path: '/website',
title: '网址导航'
}
]
]);

View 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