|
@ -0,0 +1,29 @@
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
coverage
|
||||||
|
*.local
|
||||||
|
|
||||||
|
/cypress/videos/
|
||||||
|
/cypress/screenshots/
|
||||||
|
|
||||||
|
components.d.ts
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 daidai
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
322
README.md
|
@ -0,0 +1,322 @@
|
||||||
|
|
||||||
|
## 项目描述
|
||||||
|
|
||||||
|
[IofTV-Screen](https://gitee.com/daidaibg/IofTV-Screen/tree/main) 的 Vue3+vite版本,
|
||||||
|
|
||||||
|
### 与vue2版本对比
|
||||||
|
|
||||||
|
#### 功能
|
||||||
|
|
||||||
|
功能采用与vue2版本相同功能
|
||||||
|
|
||||||
|
因为要与vue2版本相同功能,有些组件不兼容vue3版本,例如:胶囊柱图,数字滚动皆重新封装为组件,整体来说,功能属实相同。根据自己需求选择[vue2](#vue2版本地址)版本与[vue3](#本项目地址 vue3+vite)版本
|
||||||
|
|
||||||
|
#### 样式
|
||||||
|
|
||||||
|
进行微调,整体看着更加美观
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
- 项目需要全屏展示(按 F11)。
|
||||||
|
|
||||||
|
- 项目部分区域使用了全局注册方式,增加了打包体积,在实际运用中请使用 **按需引入**。
|
||||||
|
|
||||||
|
- 项目环境:Vite、Echarts、Npm、Node,axios,mock,vue3。
|
||||||
|
|
||||||
|
- 请拉取 master 分支的代码,其余分支是开发分支。
|
||||||
|
|
||||||
|
- 在项目public目录下存放地图数据合集,根据地市编存放。
|
||||||
|
|
||||||
|
|
||||||
|
友情链接:
|
||||||
|
|
||||||
|
1. [Vue 官方文档](https://cn.vuejs.org/)
|
||||||
|
3. [echarts 实例](https://gitee.com/link?target=https%3A%2F%2Fecharts.apache.org%2Fexamples%2Fzh%2Findex.html),[echarts API 文档](https://gitee.com/link?target=https%3A%2F%2Fecharts.apache.org%2Fzh%2Fapi.html%23echarts)
|
||||||
|
4. [mock.js官网](http://mockjs.com/examples.html)
|
||||||
|
5. [axios官网](https://axios-http.com/)
|
||||||
|
|
||||||
|
**项目展示**
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### 项目预览地址
|
||||||
|
|
||||||
|
[https://www.daidaibg.com/bigscreen-vue3](https://www.daidaibg.com/bigscreen-vue3)
|
||||||
|
|
||||||
|
### 项目仓库地址
|
||||||
|
|
||||||
|
#### 本项目地址 vue3+vite
|
||||||
|
|
||||||
|
**github地址**
|
||||||
|
|
||||||
|
[https://github.com/daidaibg/IofTV-Screen-Vue3](https://github.com/daidaibg/IofTV-Screen-Vue3)
|
||||||
|
|
||||||
|
**Gitee地址**
|
||||||
|
|
||||||
|
[https://gitee.com/daidaibg/IofTV-Screen-Vue3](https://gitee.com/daidaibg/IofTV-Screen-Vue3)
|
||||||
|
|
||||||
|
#### vue2版本地址
|
||||||
|
|
||||||
|
**github地址**
|
||||||
|
|
||||||
|
[https://github.com/daidaibg/IofTV-Screen](https://github.com/daidaibg/IofTV-Screen)
|
||||||
|
|
||||||
|
**Gitee地址**
|
||||||
|
|
||||||
|
[https://gitee.com/daidaibg/IofTV-Screen](https://gitee.com/daidaibg/IofTV-Screen)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 采用自适应组件方式,
|
||||||
|
|
||||||
|
### 滚动设置,自适应设置
|
||||||
|
|
||||||
|
项目中可以进行滚动配置,内容是否滚动
|
||||||
|
|
||||||
|
点击右上角设置按钮
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
可以进行以下配置,可以自行代码中进行修改或增加配置
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 2、主要文件介绍
|
||||||
|
|
||||||
|
| 文件 | 作用/功能 |
|
||||||
|
| ----------------- | ------------------------------------------------------------ |
|
||||||
|
| main.js | 主目录文件,引入 Echart/DataV 等文件 |
|
||||||
|
| utils | 工具函数与 mixins 函数等 |
|
||||||
|
| views/ home.vue | 项目主结构 |
|
||||||
|
| views/其余文件 | 界面各个区域组件(按照位置来命名) |
|
||||||
|
| assets | 静态资源目录,放置 logo 与背景图片 |
|
||||||
|
| assets / css/ | 通用 CSS 文件,全局项目快捷样式调节 |
|
||||||
|
| components/echart | 所有 echart 图表(按照位置来命名) |
|
||||||
|
| common/... | 全局封装的 ECharts 和 flexible 插件代码(适配屏幕尺寸,可定制化修改) |
|
||||||
|
| api/api.js | 接口封装文件 |
|
||||||
|
| mock | 模拟数据接口地址 |
|
||||||
|
|
||||||
|
###
|
||||||
|
|
||||||
|
## 使用介绍
|
||||||
|
|
||||||
|
### 安装
|
||||||
|
|
||||||
|
```npm
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
### 启动
|
||||||
|
|
||||||
|
```npm
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### 取消mock模拟数据
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// src\main.ts文件
|
||||||
|
把下面两行代码注释掉就可以了。
|
||||||
|
import { mockXHR } from "@/mock/index";
|
||||||
|
mockXHR()
|
||||||
|
```
|
||||||
|
|
||||||
|
##
|
||||||
|
|
||||||
|
## 公用组件
|
||||||
|
|
||||||
|
封装了除面条外个别用到的组件
|
||||||
|
|
||||||
|
### 自适应缩放组件
|
||||||
|
|
||||||
|
#### 注意
|
||||||
|
|
||||||
|
采用Scale方式,会自动给组件父元素添加overflow:hidden
|
||||||
|
|
||||||
|
#### 使用
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<scale-screen width="1920" height="1080">
|
||||||
|
<div>
|
||||||
|
content
|
||||||
|
</div>
|
||||||
|
</scale-screen>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ScaleScreen from 'scale-screen'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name:'Demo',
|
||||||
|
components:{
|
||||||
|
VScaleScreen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### API
|
||||||
|
|
||||||
|
| 属性 | 说明 | 类型 | 默认值 |
|
||||||
|
| ------------ | ------------------------------------------------------------ | -------------------------------- | ------ |
|
||||||
|
| selfAdaption | 是否进行自适应 | Boolean | true |
|
||||||
|
| width | 大屏宽度 | `Number` or `String` | 1920 |
|
||||||
|
| height | 大屏高度 | `Number` or `String` | 1080 |
|
||||||
|
| autoScale | 自适应配置,配置为boolean类型时,为启动或者关闭自适应,配置为对象时,若x为true,x轴产生边距,y为true时,y轴产生边距,启用fullScreen时此配置失效 | Boolean or {x:boolean,y:boolean} | true |
|
||||||
|
| delay | 窗口变化防抖延迟时间 | Number | 500 |
|
||||||
|
| fullScreen | 全屏自适应,启用此配置项时会存在拉伸效果,同时autoScale失效,非必要情况下不建议开启 | Boolean | false |
|
||||||
|
| boxStyle | 修改容器样式,如居中展示时侧边背景色,符合Vue双向绑定style标准格式 | Object | null |
|
||||||
|
| wrapperStyle | 修改自适应区域样式,符合Vue双向绑定style标准格式 | Object | null |
|
||||||
|
|
||||||
|
|
||||||
|
### 外边框
|
||||||
|
|
||||||
|
因为我的项目外边框几乎一样,还有title,所以封装了此组件。
|
||||||
|
|
||||||
|
根据自己需求更改,更换外边框(src\components\item-wrap\item-wrap.vue)下更换。
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<ItemWrap
|
||||||
|
title="我是title"
|
||||||
|
>
|
||||||
|
<div>我是谁?</div>
|
||||||
|
</ItemWrap>
|
||||||
|
```
|
||||||
|
|
||||||
|
| 参数 | 描述 | 默认值 | 类型 | 可选值 |
|
||||||
|
| :---: | :--: | :----: | :----: | :----: |
|
||||||
|
| title | 标头 | - | string | - |
|
||||||
|
|
||||||
|
### CountUp 数字滚动
|
||||||
|
|
||||||
|
以下属性同 coutup.js 配置项(same as countup.js properties)
|
||||||
|
|
||||||
|
#### Props
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| -------- | ---------------- | ------- | ------------------------------------------------------------ |
|
||||||
|
| endVal | Number \| String | - | 结束值 |
|
||||||
|
| startVal | Number \| String | 0 | 起始值 |
|
||||||
|
| duration | Number | 2.5 | 动画时长,单位:秒 |
|
||||||
|
| options | Object | - | [countUp.js](https://github.com/inorganik/countUp.js) options 配置项 |
|
||||||
|
|
||||||
|
以下为组件特有属性(extension properties)
|
||||||
|
|
||||||
|
| Name | Type | Default | Description |
|
||||||
|
| -------- | ----------------- | ------- | ----------------------------- |
|
||||||
|
| autoplay | Boolean | true | 是否自动计数 |
|
||||||
|
| loop | Boolean \| Number | false | 循环次数,有限次数 / 无限循环 |
|
||||||
|
| delay | Number | 0 | loop 循环的间隔时间,单位:秒 |
|
||||||
|
|
||||||
|
#### 插槽(slots)
|
||||||
|
|
||||||
|
| Name | Description |
|
||||||
|
| ------ | ----------- |
|
||||||
|
| prefix | 前缀 |
|
||||||
|
| suffix | 后缀 |
|
||||||
|
|
||||||
|
#### 事件(Events)
|
||||||
|
|
||||||
|
| Name | Description | return |
|
||||||
|
| --------- | -------------------------- | ------------ |
|
||||||
|
| @init | CountUp 实例初始化完成触发 | CountUp 实例 |
|
||||||
|
| @finished | 计数结束时触发 | - |
|
||||||
|
|
||||||
|
#### countup.js 配置项说明
|
||||||
|
```ts
|
||||||
|
interface CountUpOptions {
|
||||||
|
startVal?: number // number to start at (0) 开始数值,默认 0
|
||||||
|
decimalPlaces?: number // number of decimal places (0) 小数点 位数
|
||||||
|
duration?: number // animation duration in seconds (2) 动画时长
|
||||||
|
useGrouping?: boolean // example: 1,000 vs 1000 (true) 是否使用千分位
|
||||||
|
useEasing?: boolean // ease animation (true) 是否开启动画过渡,默认动画函数为easeOutExpo
|
||||||
|
smartEasingThreshold?: number // smooth easing for large numbers above this if useEasing (999)
|
||||||
|
smartEasingAmount?: number // amount to be eased for numbers above threshold (333)
|
||||||
|
separator?: string // grouping separator (',') 千分位分隔符
|
||||||
|
decimal?: string // decimal ('.') 小数点分隔符
|
||||||
|
// easingFn: easing function for animation (easeOutExpo) 动画函数
|
||||||
|
easingFn?: (t: number, b: number, c: number, d: number) => number
|
||||||
|
formattingFn?: (n: number) => string // this function formats result 格式化结果
|
||||||
|
prefix?: string // text prepended to result 数值前缀
|
||||||
|
suffix?: string // text appended to result 数值后缀
|
||||||
|
numerals?: string[] // numeral glyph substitution 数字符号替换 0 - 9,例如替换为 [a,b,c,d,e,f,g,h,i,j]
|
||||||
|
enableScrollSpy?: boolean // start animation when target is in view 在可视范围内才开始动画
|
||||||
|
scrollSpyDelay?: number // delay (ms) after target comes into view 目标进入可视范围内后的延迟时间(毫秒)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 胶囊柱图
|
||||||
|
|
||||||
|
#### Props
|
||||||
|
|
||||||
|
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
| :----: | :------: | :-------------: | :-----------------------: | :-----: |
|
||||||
|
| data | 柱数据 | `Array<Object>` | [data属性](#data属性) | `[]` |
|
||||||
|
| config | 基础配置 | Object | [config属性](#config属性) | `false` |
|
||||||
|
|
||||||
|
#### config属性
|
||||||
|
|
||||||
|
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
| :-------: | :------: | :-------------: | :----: | :-----: |
|
||||||
|
| unit | 单位 | `String` | --- | `''` |
|
||||||
|
| colors | 环颜色 | `Array<String>` | [1] | [2] |
|
||||||
|
| showValue | 显示数值 | `Boolean` | --- | `false` |
|
||||||
|
|
||||||
|
#### 注释config注释
|
||||||
|
|
||||||
|
[1] 颜色支持`hex|rgb|rgba|颜色关键字`等四种类型。
|
||||||
|
|
||||||
|
[2] 默认配色为`['#37a2da', '#32c5e9', '#67e0e3', '#9fe6b8', '#ffdb5c', '#ff9f7f', '#fb7293']`。
|
||||||
|
|
||||||
|
#### data属性
|
||||||
|
|
||||||
|
| 属性 | 说明 | 类型 | 可选值 | 默认值 |
|
||||||
|
| :---: | :------: | :------: | :----: | :----: |
|
||||||
|
| name | 柱名称 | `String` | --- | --- |
|
||||||
|
| value | 柱对应值 | `Number` | --- | --- |
|
||||||
|
|
||||||
|
### 无缝轮播组件
|
||||||
|
|
||||||
|
看此文档 优化次源码
|
||||||
|
|
||||||
|
[https://doc.wssio.com/opensource/vue3-seamless-scroll/](https://doc.wssio.com/opensource/vue3-seamless-scroll/)
|
||||||
|
|
||||||
|
## 中间地图
|
||||||
|
|
||||||
|
### 南海显隐控制
|
||||||
|
|
||||||
|
根据需求来,**修改此值请刷新页面**
|
||||||
|
|
||||||
|
```indexs/center-map.vue``` 文件中```isSouthChinaSea```变量 默认不显示南海(false),为```true```的时候显示南海
|
||||||
|
|
||||||
|
```
|
||||||
|
isSouthChinaSea:false,//默认不显示南海,改为true可显示南海
|
||||||
|
```
|
||||||
|
|
||||||
|
## 全局参数
|
||||||
|
|
||||||
|
### filter
|
||||||
|
|
||||||
|
监测数据项统一过滤,保留两位小数。
|
||||||
|
|
||||||
|
```vue
|
||||||
|
{{10.23123|montionFilter }}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 大屏交流反馈(面条的群)
|
||||||
|
|
||||||
|
### 大屏QQ群
|
||||||
|
|
||||||
|
QQ群号:
|
||||||
|
|
||||||
|
一群:713105837 (已满)
|
||||||
|
|
||||||
|
二群:495755841
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
/* eslint-disable */
|
||||||
|
/* prettier-ignore */
|
||||||
|
// @ts-nocheck
|
||||||
|
// noinspection JSUnusedGlobalSymbols
|
||||||
|
// Generated by unplugin-auto-import
|
||||||
|
export {}
|
||||||
|
declare global {
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>IofTV-Screen-Vue3</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
<script type="module" src="/src/main.ts"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,45 @@
|
||||||
|
{
|
||||||
|
"name": "test",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build:old": "run-p type-check build-only",
|
||||||
|
"build": "vite build",
|
||||||
|
"preview": "vite preview --port 4173",
|
||||||
|
"build-only": "vite build",
|
||||||
|
"type-check": "vue-tsc --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.6.8",
|
||||||
|
"countup.js": "^2.8.0",
|
||||||
|
"dayjs": "^1.11.10",
|
||||||
|
"echarts": "^5.5.0",
|
||||||
|
"element-plus": "^2.6.2",
|
||||||
|
"js-cookie": "^3.0.5",
|
||||||
|
"mockjs": "^1.1.0",
|
||||||
|
"pinia": "^2.1.7",
|
||||||
|
"vue": "^3.4.21",
|
||||||
|
"vue-echarts": "^6.6.9",
|
||||||
|
"vue-router": "^4.3.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/js-cookie": "^3.0.6",
|
||||||
|
"@types/mockjs": "^1.0.10",
|
||||||
|
"@types/node": "^20.11.30",
|
||||||
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
|
"@vue/tsconfig": "^0.5.1",
|
||||||
|
"@vueuse/core": "^10.9.0",
|
||||||
|
"autoprefixer": "^10.4.19",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"postcss": "^8.4.38",
|
||||||
|
"sass": "^1.72.0",
|
||||||
|
"tailwindcss": "^3.4.3",
|
||||||
|
"typescript": "~5.4.3",
|
||||||
|
"unplugin-auto-import": "^0.17.5",
|
||||||
|
"unplugin-element-plus": "^0.8.0",
|
||||||
|
"unplugin-vue-components": "^0.26.0",
|
||||||
|
"vite": "^5.2.6",
|
||||||
|
"vue-tsc": "^2.0.7"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
export default {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
}
|
After Width: | Height: | Size: 162 KiB |
|
@ -0,0 +1 @@
|
||||||
|
{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"adcode":710000,"name":"台湾省","center":[121.509062,25.044332],"centroid":[120.971485,23.749452],"childrenNum":0,"level":"province","acroutes":[100000],"parent":{"adcode":100000}},"geometry":{"type":"MultiPolygon","coordinates":[[[[120.443558,22.441245],[120.517584,22.408536],[120.569903,22.361728],[120.640505,22.241347],[120.659209,22.15432],[120.662001,22.066983],[120.651464,22.033165],[120.667691,21.983168],[120.70157,21.927065],[120.743246,21.915569],[120.78155,21.923957],[120.85468,21.883333],[120.87291,21.897387],[120.866482,21.98436],[120.907315,22.033208],[120.904154,22.119757],[120.914955,22.302718],[120.981658,22.528305],[121.015009,22.584168],[121.033292,22.650725],[121.078498,22.669656],[121.170544,22.723133],[121.210481,22.770665],[121.237931,22.836327],[121.324708,22.945666],[121.354687,23.01006],[121.370388,23.084347],[121.409535,23.102669],[121.430294,23.137196],[121.415015,23.195973],[121.440358,23.272096],[121.479558,23.3223],[121.497788,23.419789],[121.521497,23.483198],[121.523078,23.538708],[121.587778,23.76102],[121.621604,23.92075],[121.659381,24.006893],[121.639992,24.064276],[121.643838,24.097713],[121.678085,24.133906],[121.689044,24.174401],[121.809172,24.339055],[121.826717,24.423579],[121.867498,24.478978],[121.885464,24.529677],[121.892524,24.617912],[121.862598,24.671515],[121.837993,24.76015],[121.845053,24.836269],[121.932883,24.938645],[122.012178,25.001469],[121.980776,25.03079],[121.947425,25.031955],[121.917077,25.137908],[121.842155,25.135332],[121.782407,25.160425],[121.750531,25.160716],[121.707327,25.191493],[121.700319,25.226913],[121.655324,25.241859],[121.623026,25.294694],[121.584986,25.308926],[121.535038,25.307515],[121.444415,25.270624],[121.413487,25.238912],[121.371864,25.159885],[121.319281,25.140691],[121.209322,25.127104],[121.133135,25.078728],[121.102102,25.075153],[121.024704,25.040479],[121.009688,24.993649],[120.960899,24.940227],[120.908475,24.852012],[120.892299,24.767526],[120.823753,24.688321],[120.762371,24.658335],[120.688661,24.600678],[120.64277,24.490172],[120.589187,24.432354],[120.546299,24.370413],[120.521009,24.312038],[120.470534,24.24259],[120.451461,24.182691],[120.392029,24.11824],[120.316158,23.984881],[120.278276,23.927798],[120.245768,23.840553],[120.175377,23.807385],[120.102773,23.700981],[120.094817,23.587466],[120.121741,23.504664],[120.107831,23.341264],[120.081434,23.29191],[120.018947,23.073115],[120.029537,23.048623],[120.131382,23.002118],[120.149138,22.896715],[120.200403,22.721101],[120.274272,22.560181],[120.297191,22.531315],[120.443558,22.441245]]],[[[124.542984,25.903911],[124.586346,25.913777],[124.572805,25.93974],[124.541825,25.931031],[124.542984,25.903911]]],[[[123.445286,25.725966],[123.472104,25.713024],[123.508933,25.723237],[123.514834,25.751226],[123.483063,25.768587],[123.444496,25.746514],[123.445286,25.725966]]],[[[119.64597,23.55091],[119.701081,23.550657],[119.678057,23.600041],[119.610089,23.603953],[119.594388,23.577245],[119.566306,23.584732],[119.562565,23.530377],[119.573788,23.505885],[119.609141,23.503864],[119.64597,23.55091]]],[[[123.667207,25.914066],[123.707092,25.916873],[123.678008,25.938667],[123.667207,25.914066]]],[[[119.506031,23.625567],[119.505241,23.575814],[119.472416,23.557136],[119.523207,23.563699],[119.525578,23.624895],[119.506031,23.625567]]],[[[119.49739,23.386683],[119.495125,23.350156],[119.516885,23.349903],[119.49739,23.386683]]],[[[119.557454,23.666474],[119.604083,23.616989],[119.615516,23.660925],[119.586485,23.675974],[119.557454,23.666474]]],[[[121.46823,22.676644],[121.476502,22.64166],[121.513541,22.631833],[121.5147,22.67639],[121.46823,22.676644]]],[[[121.510538,22.087185],[121.507693,22.048523],[121.534089,22.022146],[121.594522,21.995382],[121.604586,22.022699],[121.575028,22.037122],[121.575607,22.084421],[121.510538,22.087185]]],[[[122.097533,25.500168],[122.093581,25.47183],[122.124825,25.475932],[122.097533,25.500168]]],[[[119.421467,23.216684],[119.421309,23.18935],[119.453396,23.217697],[119.421467,23.216684]]],[[[120.355042,22.327259],[120.395454,22.342287],[120.383072,22.355573],[120.355042,22.327259]]]]}}]}
|
|
@ -0,0 +1,12 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { RouterView } from 'vue-router'
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<RouterView />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
|
@ -0,0 +1,255 @@
|
||||||
|
/*
|
||||||
|
* @LastEditors: 张宁
|
||||||
|
* @LastEditTime: 2024-05-21 15:07:36
|
||||||
|
*/
|
||||||
|
import axios from "axios";
|
||||||
|
import type { AxiosRequestConfig, AxiosResponse } from "axios";
|
||||||
|
import { StorageEnum, RequestEnum } from "@/enums";
|
||||||
|
import { getLocalStorage,getToken } from "@/utils";
|
||||||
|
|
||||||
|
import UtilVar from "../config/UtilVar";
|
||||||
|
let baseUrl = UtilVar.baseUrl;
|
||||||
|
const CancelToken = axios.CancelToken;
|
||||||
|
|
||||||
|
export { baseUrl };
|
||||||
|
// axios.defaults.withCredentials = true;
|
||||||
|
// 添加请求拦截器
|
||||||
|
axios.interceptors.request.use(
|
||||||
|
function (config: AxiosRequestConfig): any {
|
||||||
|
// 在发送请求之前做些什么 传token
|
||||||
|
// let token: any = getLocalStorage(StorageEnum.GB_TOKEN_STORE);
|
||||||
|
let token: any = getToken();
|
||||||
|
if (token) {
|
||||||
|
// @ts-ignore
|
||||||
|
// config.headers.common[RequestEnum.GB_TOKEN_KEY] = token;
|
||||||
|
config.headers['Authorization'] = token
|
||||||
|
}
|
||||||
|
// @ts-ignore
|
||||||
|
config.headers["Content-Type"] = "application/json;charset=utf-8";
|
||||||
|
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
function (error: any) {
|
||||||
|
// 对请求错误做些什么
|
||||||
|
console.log(error);
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export type Params = { [key: string]: string | number };
|
||||||
|
export type FileConfig = {
|
||||||
|
setCancel?: Function;
|
||||||
|
onProgress?: Function;
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @响应拦截
|
||||||
|
*/
|
||||||
|
axios.interceptors.response.use(
|
||||||
|
(response: AxiosResponse) => {
|
||||||
|
// console.log("response", response);
|
||||||
|
if (response.status !== 200) {
|
||||||
|
return Promise.reject(response);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @code 登录过期 token验证失败 根据后端调
|
||||||
|
*/
|
||||||
|
if (response.data.code == UtilVar.code) {
|
||||||
|
// router.push("/login")
|
||||||
|
return Promise.resolve(response);
|
||||||
|
}
|
||||||
|
return Promise.resolve(response);
|
||||||
|
},
|
||||||
|
(error: any) => {
|
||||||
|
console.log("error", error);
|
||||||
|
let err = {
|
||||||
|
success: false,
|
||||||
|
msg: "未知异常,请联系管理员!",
|
||||||
|
code: 400,
|
||||||
|
};
|
||||||
|
if (JSON.stringify(error).indexOf("Network Error") != -1) {
|
||||||
|
err.msg = "网络错误或服务错误!";
|
||||||
|
}
|
||||||
|
if (error.message == "canceled") {
|
||||||
|
err.msg = "取消请求";
|
||||||
|
err.code = 488;
|
||||||
|
}
|
||||||
|
// console.log(err);
|
||||||
|
return Promise.reject(err);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
//判断是否是加密参数,是的话处理
|
||||||
|
let isEncryptionParam = (params: Params) => {
|
||||||
|
return params;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @description: get 请求方法
|
||||||
|
* @param {string} url 请求地址
|
||||||
|
* @param {Params} params 请求参数
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
export const GET = async (url: string, params: Params): Promise<any> => {
|
||||||
|
try {
|
||||||
|
params = isEncryptionParam(params);
|
||||||
|
const data = await axios.get(`${baseUrl}${url}`, {
|
||||||
|
params: params,
|
||||||
|
});
|
||||||
|
return data.data;
|
||||||
|
} catch (error: any) {
|
||||||
|
return Promise.reject(error.msg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @description: post请求方法
|
||||||
|
* @param {any} url
|
||||||
|
* @param {any} params
|
||||||
|
* @return {any}
|
||||||
|
*/
|
||||||
|
export const POST = async (url: string, params: Params): Promise<any> => {
|
||||||
|
try {
|
||||||
|
params = isEncryptionParam(params);
|
||||||
|
const data = await axios.post(`${baseUrl}${url}`, params);
|
||||||
|
return data.data;
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @description: 没有基地址 访问根目录下文件
|
||||||
|
* @param {string} url
|
||||||
|
* @param {Params} params
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
export const GETNOBASE = async (url: string, params?: Params): Promise<any> => {
|
||||||
|
try {
|
||||||
|
const data = await axios.get(url, {
|
||||||
|
params: params,
|
||||||
|
});
|
||||||
|
return data.data;
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 定义文件类型提交方法
|
||||||
|
interface fileconfigs {
|
||||||
|
[headers: string]: {
|
||||||
|
"Content-Type": string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let configs: fileconfigs = {
|
||||||
|
headers: { "Content-Type": "multipart/form-data" },
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @description: @文件类型提交方法
|
||||||
|
* @param {string} url
|
||||||
|
* @param {Params} params
|
||||||
|
* @param {FileConfig} config
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
export const FILEPOST = async (url: string, params: Params, config: FileConfig = {}): Promise<any> => {
|
||||||
|
try {
|
||||||
|
const data = await axios.post(`${baseUrl}${url}`, params, {
|
||||||
|
...configs,
|
||||||
|
cancelToken: new CancelToken(function executor(c: any) {
|
||||||
|
config.setCancel && config.setCancel(c);
|
||||||
|
}),
|
||||||
|
// 上传进度
|
||||||
|
onUploadProgress: (e: any) => {
|
||||||
|
if (e.total > 0) {
|
||||||
|
e.percent = (e.loaded / e.total) * 100;
|
||||||
|
}
|
||||||
|
config.onProgress && config.onProgress(e);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载文档流
|
||||||
|
* @param {config.responseType} 下载文件流根据后端 配置 arraybuffer || blod
|
||||||
|
*/
|
||||||
|
export const FILE = async (config: FileConfig = {}) => {
|
||||||
|
try {
|
||||||
|
const data = await axios({
|
||||||
|
method: config.method || "get",
|
||||||
|
url: `${baseUrl}${config.url}`,
|
||||||
|
data: config.body || {},
|
||||||
|
params: config.param || {},
|
||||||
|
responseType: config.responseType || "blod",
|
||||||
|
onDownloadProgress: (e: any) => {
|
||||||
|
// console.log(e,e.currentTarget)
|
||||||
|
// if (e.currentTarget.response.size > 0) {
|
||||||
|
// e.percent = e.loaded / e.currentTarget.response.size * 100;
|
||||||
|
// }
|
||||||
|
// event.srcElement.getResponseHeader('content-length')
|
||||||
|
config.onProgress && config.onProgress(e);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PUT = async (url: string, params: Params) => {
|
||||||
|
try {
|
||||||
|
params = isEncryptionParam(params);
|
||||||
|
const data = await axios.put(`${baseUrl}${url}`, params);
|
||||||
|
return data.data;
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export const DELETE = async (url: string, params: Params) => {
|
||||||
|
// console.log(params)
|
||||||
|
try {
|
||||||
|
params = isEncryptionParam(params);
|
||||||
|
const data = await axios.delete(`${baseUrl}${url}`, { data: params });
|
||||||
|
return data.data;
|
||||||
|
} catch (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// switch (error.response?.status) {
|
||||||
|
// case 400:
|
||||||
|
// error.message = '请求错误(400)';
|
||||||
|
// break;
|
||||||
|
// case 401:
|
||||||
|
// error.message = '未授权(401)';
|
||||||
|
// break;
|
||||||
|
// case 403:
|
||||||
|
// error.message = '拒绝访问(403)';
|
||||||
|
// break;
|
||||||
|
// case 404:
|
||||||
|
// error.message = '请求出错(404)';
|
||||||
|
// break;
|
||||||
|
// case 408:
|
||||||
|
// error.message = '请求超时(408)';
|
||||||
|
// break;
|
||||||
|
// case 500:
|
||||||
|
// error.message = '服务器错误(500)';
|
||||||
|
// break;
|
||||||
|
// case 501:
|
||||||
|
// error.message = '服务未实现(501)';
|
||||||
|
// break;
|
||||||
|
// case 502:
|
||||||
|
// error.message = '网络错误(502)';
|
||||||
|
// break;
|
||||||
|
// case 503:
|
||||||
|
// error.message = '服务不可用(503)';
|
||||||
|
// break;
|
||||||
|
// case 504:
|
||||||
|
// error.message = '网络超时(504)';
|
||||||
|
// break;
|
||||||
|
// case 505:
|
||||||
|
// error.message = 'HTTP版本不受支持(505)';
|
||||||
|
// break;
|
||||||
|
// default:
|
||||||
|
// error.message = `连接出错(${error.response?.status})!`;
|
||||||
|
// }
|
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* @Author: daidai
|
||||||
|
* @Date: 2021-12-23 11:18:37
|
||||||
|
|
||||||
|
* @LastEditTime: 2024-03-28 16:07:20
|
||||||
|
* @FilePath: \web-pc-svn\src\api\modules\index.js
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {GETNOBASE} from "./api";
|
||||||
|
export * from "./modules/index"
|
||||||
|
export * from "./modules/test"
|
||||||
|
export {GETNOBASE}
|
|
@ -0,0 +1,54 @@
|
||||||
|
import {GET,POST,FILE,FILEPOST,PUT,GETNOBASE} from "../api";
|
||||||
|
const indexUrl= {
|
||||||
|
'leftTop':'/bigscreen/countDeviceNum',//左上
|
||||||
|
'leftCenter':'/bigscreen/countUserNum',//左中
|
||||||
|
"centerMap":"/bigscreen/centerMap",
|
||||||
|
"centerBottom":"/bigscreen/installationPlan",
|
||||||
|
|
||||||
|
'leftBottom':"/bigscreen/leftBottom", //坐下
|
||||||
|
'rightTop':"/bigscreen/alarmNum", //报警次数
|
||||||
|
'rightBottom':'/bigscreen/rightBottom',//右下
|
||||||
|
'rightCenter':'/bigscreen/ranking',// 报警排名
|
||||||
|
}
|
||||||
|
|
||||||
|
export default indexUrl
|
||||||
|
|
||||||
|
/**左上--设备内总览 */
|
||||||
|
export const countDeviceNum=(param:any={})=>{
|
||||||
|
return GET(indexUrl.leftTop,param)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**左中--用户总览 */
|
||||||
|
export const countUserNum=(param:any={})=>{
|
||||||
|
return GET(indexUrl.leftCenter,param)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**左下--设备提醒 */
|
||||||
|
export const leftBottom=(param:any={})=>{
|
||||||
|
return GET(indexUrl.leftBottom,param)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**中上--地图 */
|
||||||
|
export const centerMap=(param:any={})=>{
|
||||||
|
return GET(indexUrl.centerMap,param)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**中下--安装计划 */
|
||||||
|
export const installationPlan=(param:any={})=>{
|
||||||
|
return GET(indexUrl.centerBottom,param)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**右上--报警次数 */
|
||||||
|
export const alarmNum=(param:any={})=>{
|
||||||
|
return GET(indexUrl.rightTop,param)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**右中--报警排名 */
|
||||||
|
export const ranking=(param:any={})=>{
|
||||||
|
return GET(indexUrl.rightCenter,param)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**右下--设备状态 */
|
||||||
|
export const rightBottom=(param:any={})=>{
|
||||||
|
return GET(indexUrl.rightBottom,param)
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
import {GET,POST,FILE,FILEPOST,PUT,GETNOBASE} from "../api";
|
||||||
|
export const testGet=(param:any={})=>{
|
||||||
|
return GET('/api/TeacherManagement/AdminPCIndex',param)
|
||||||
|
}
|
|
@ -0,0 +1,98 @@
|
||||||
|
html,body{
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#app{
|
||||||
|
.content_wrap{
|
||||||
|
color: #d3d6dd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
html .el-message {
|
||||||
|
--yh-bg-color-container:#242424;
|
||||||
|
--yh-shadow-3: 0 16px 24px rgba(0, 0, 0, .14), 0 6px 30px rgba(0, 0, 0, 12%), 0 8px 10px rgba(0, 0, 0, 20%);
|
||||||
|
--yh-shadow-inset-top: inset 0 .5px 0 #5e5e5e;
|
||||||
|
--yh-shadow-inset-right: inset .5px 0 0 #5e5e5e;
|
||||||
|
--yh-shadow-inset-bottom: inset 0 -.5px 0 #5e5e5e;
|
||||||
|
--yh-shadow-inset-left: inset -.5px 0 0 #5e5e5e;
|
||||||
|
--yh-text-color-primary:rgba(255, 255, 255, .9);
|
||||||
|
--yh-brand-color: #0052d9;
|
||||||
|
--yh-success-color: #059465;
|
||||||
|
--yh-error-color: #c64751;
|
||||||
|
--yh-warning-color: #cf6e2d;
|
||||||
|
|
||||||
|
background-color: var(--yh-bg-color-container) ;
|
||||||
|
box-shadow: var(--yh-shadow-3), var(--yh-shadow-inset-top),
|
||||||
|
var(--yh-shadow-inset-right), var(--yh-shadow-inset-bottom),
|
||||||
|
var(--yh-shadow-inset-left);
|
||||||
|
border: none ;
|
||||||
|
color: var(--yh-text-color-primary) ;
|
||||||
|
margin-top: 90px;
|
||||||
|
.el-message__icon {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.el-message--info .el-message__icon {
|
||||||
|
color: var(--yh-brand-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.el-message--success .el-message__icon {
|
||||||
|
color: var(--yh-success-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.el-message--warning .el-message__icon {
|
||||||
|
color: var(--yh-warning-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.el-message--error .el-message__icon {
|
||||||
|
color: var(--yh-error-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-message__content {
|
||||||
|
color: var(--yh-text-color-primary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.beautify-scroll-def {
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
//滚动条的设置
|
||||||
|
background-color: rgba(14, 59, 150, 0);
|
||||||
|
background-clip: padding-box;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
//滚动条的设置
|
||||||
|
background-color: rgba(14, 59, 150, 0.5);
|
||||||
|
background-clip: padding-box;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-track-piece {
|
||||||
|
//滚动条凹槽的颜色,还可以设置边框属性
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
//滚动条的宽度
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
&::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: rgba(14, 59, 150, .8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-content {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
min-height: calc(100% - 60px);
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
@import "tailwindcss/base";
|
||||||
|
@import "tailwindcss/components";
|
||||||
|
@import "tailwindcss/utilities";
|
||||||
|
|
||||||
|
/* @tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities; */
|
|
@ -0,0 +1 @@
|
||||||
|
$primary-color: #1890ff;
|
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 26 KiB |
After Width: | Height: | Size: 25 KiB |
After Width: | Height: | Size: 62 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 323 B |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 8.6 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 289 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 2.1 KiB |
|
@ -0,0 +1,3 @@
|
||||||
|
import MessageContent from './index.vue';
|
||||||
|
|
||||||
|
export default MessageContent ;
|
|
@ -0,0 +1,8 @@
|
||||||
|
<!-- eslint-disable vue/valid-template-root -->
|
||||||
|
<template></template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
//挂载在 window 方便与在js中使用
|
||||||
|
window['$message'] = ElMessage
|
||||||
|
</script>
|
|
@ -0,0 +1,146 @@
|
||||||
|
<script lang="ts">
|
||||||
|
export type { CountUp as ICountUp, CountUpOptions } from 'countup.js'
|
||||||
|
export default {
|
||||||
|
name: 'CountUp'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
|
import { CountUp } from 'countup.js'
|
||||||
|
import type { CountUpOptions } from 'countup.js'
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
// 结束数值
|
||||||
|
endVal: number | string
|
||||||
|
// 开始数值
|
||||||
|
startVal?: number | string
|
||||||
|
// 动画时长,单位 s
|
||||||
|
duration?: number | string
|
||||||
|
// 是否自动计数
|
||||||
|
autoplay?: boolean
|
||||||
|
// 循环次数,有限次数 / 无限循环
|
||||||
|
loop?: boolean | number | string
|
||||||
|
// 延时,单位 s
|
||||||
|
delay?: number
|
||||||
|
// countup 配置项
|
||||||
|
options?: CountUpOptions
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
startVal: 0,
|
||||||
|
duration: 2.5,
|
||||||
|
autoplay: true,
|
||||||
|
loop: false,
|
||||||
|
delay: 0,
|
||||||
|
options: undefined
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const emits = defineEmits<{
|
||||||
|
// countup init complete
|
||||||
|
(event: 'init', countup: CountUp): void
|
||||||
|
// count complete
|
||||||
|
(event: 'finished'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
let elRef = ref<HTMLElement>()
|
||||||
|
let countUp = ref<CountUp>()
|
||||||
|
|
||||||
|
const initCountUp = () => {
|
||||||
|
if (!elRef.value) return
|
||||||
|
const startVal = Number(props.startVal)
|
||||||
|
const endVal = Number(props.endVal)
|
||||||
|
const duration = Number(props.duration)
|
||||||
|
countUp.value = new CountUp(elRef.value, endVal, {
|
||||||
|
startVal,
|
||||||
|
duration,
|
||||||
|
...props.options
|
||||||
|
})
|
||||||
|
if (countUp.value.error) {
|
||||||
|
console.error(countUp.value.error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
emits('init', countUp.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const startAnim = (cb?: () => void) => {
|
||||||
|
countUp.value?.start(cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// endVal change & autoplay: true, restart animate
|
||||||
|
watch(
|
||||||
|
() => props.endVal,
|
||||||
|
(value) => {
|
||||||
|
if (props.autoplay) {
|
||||||
|
countUp.value?.update(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// loop animation
|
||||||
|
const finished = ref(false)
|
||||||
|
let loopCount = 0
|
||||||
|
const loopAnim = () => {
|
||||||
|
loopCount++
|
||||||
|
startAnim(() => {
|
||||||
|
const isTruely = typeof props.loop === 'boolean' && props.loop
|
||||||
|
if (isTruely || props.loop > loopCount) {
|
||||||
|
delay(() => {
|
||||||
|
countUp.value?.reset()
|
||||||
|
loopAnim()
|
||||||
|
}, props.delay)
|
||||||
|
} else {
|
||||||
|
finished.value = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
watch(finished, (flag) => {
|
||||||
|
if (flag) {
|
||||||
|
emits('finished')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
initCountUp()
|
||||||
|
if (props.autoplay) {
|
||||||
|
loopAnim()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
onUnmounted(() => {
|
||||||
|
cancelAnimationFrame(dalayRafId)
|
||||||
|
countUp.value?.reset()
|
||||||
|
})
|
||||||
|
|
||||||
|
let dalayRafId: number
|
||||||
|
// delay to execute callback function
|
||||||
|
const delay = (cb: () => unknown, seconds = 1) => {
|
||||||
|
let startTime: number
|
||||||
|
function count(timestamp: number) {
|
||||||
|
if (!startTime) startTime = timestamp
|
||||||
|
const diff = timestamp - startTime
|
||||||
|
if (diff < seconds * 1000) {
|
||||||
|
dalayRafId = requestAnimationFrame(count)
|
||||||
|
} else {
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dalayRafId = requestAnimationFrame(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
const restart = () => {
|
||||||
|
initCountUp()
|
||||||
|
startAnim()
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
init: initCountUp,
|
||||||
|
restart
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="countup-wrap">
|
||||||
|
<slot name="prefix"></slot>
|
||||||
|
<span ref="elRef"> </span>
|
||||||
|
<slot name="suffix"></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
|
@ -0,0 +1,3 @@
|
||||||
|
import CountUp from "./count-up.vue"
|
||||||
|
export default CountUp
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed, ref ,onBeforeUpdate, nextTick} from "vue";
|
||||||
|
import merge from "lodash/merge";
|
||||||
|
import { useElementSize } from "@vueuse/core";
|
||||||
|
import type { PropType } from "vue";
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
color: {
|
||||||
|
type: Array as unknown as PropType<[string, string]>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
default: "transparent",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const defaultColor = ["#6586ec", "#2cf7fe"];
|
||||||
|
const domRef = ref(null);
|
||||||
|
const { width, height } = useElementSize(domRef,{width:0,height:0}, { box: 'border-box' });
|
||||||
|
const mergedColor = computed<[string, string]>(() => {
|
||||||
|
return merge(defaultColor, props.color);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="dv-border-box-13 dv-border-box" ref="domRef">
|
||||||
|
<svg :width="width" :height="height" class="dv-border-svg-container">
|
||||||
|
<path
|
||||||
|
:fill="backgroundColor"
|
||||||
|
:stroke="mergedColor[0]"
|
||||||
|
:d="`
|
||||||
|
M 5 20 L 5 10 L 12 3 L 60 3 L 68 10
|
||||||
|
L ${width - 20} 10 L ${width - 5} 25
|
||||||
|
L ${width - 5} ${height - 5} L 20 ${height - 5}
|
||||||
|
L 5 ${height - 20} L 5 20
|
||||||
|
`"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<path
|
||||||
|
fill="transparent"
|
||||||
|
stroke-width="3"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-dasharray="10, 5"
|
||||||
|
:stroke="mergedColor[0]"
|
||||||
|
:d="`M 16 9 L 61 9`"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<path
|
||||||
|
fill="transparent"
|
||||||
|
stroke="{mergedColor[1]}"
|
||||||
|
:d="`M 5 20 L 5 10 L 12 3 L 60 3 L 68 10`"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<path
|
||||||
|
fill="transparent"
|
||||||
|
:stroke="mergedColor[1]"
|
||||||
|
:d="`M ${width - 5} ${height - 30} L ${width - 5} ${height - 5} L ${
|
||||||
|
width - 30
|
||||||
|
} ${height - 5}`"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
<div class="dv-border-box-content">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.dv-border-box {
|
||||||
|
position: relative;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.dv-border-svg-container {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.dv-border-box-content {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,3 @@
|
||||||
|
import BorderBox13 from "./border-box-13.vue"
|
||||||
|
|
||||||
|
export default BorderBox13
|
|
@ -0,0 +1,186 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onMounted, reactive, ref, watch } from "vue";
|
||||||
|
import type { DefaultConfigType } from "./index.d";
|
||||||
|
import cloneDeep from "lodash/cloneDeep";
|
||||||
|
import merge from "lodash/merge";
|
||||||
|
const mergedConfig = ref<any>(null);
|
||||||
|
const capsuleLength = ref<any>([]);
|
||||||
|
const capsuleValue = ref<any>([]);
|
||||||
|
const labelData = ref<any>([]);
|
||||||
|
// const labelDataLength = ref<any>([]);
|
||||||
|
|
||||||
|
const defaultConfig = reactive<DefaultConfigType>({
|
||||||
|
// Colors (hex|rgb|rgba|color keywords) ['#000', 'rgb(0, 0, 0)', 'rgba(0, 0, 0, 1)', 'red']
|
||||||
|
colors: [
|
||||||
|
"#37a2da",
|
||||||
|
"#32c5e9",
|
||||||
|
"#67e0e3",
|
||||||
|
"#9fe6b8",
|
||||||
|
"#ffdb5c",
|
||||||
|
"#ff9f7f",
|
||||||
|
"#fb7293",
|
||||||
|
],
|
||||||
|
unit: "",
|
||||||
|
showValue: false, // Show item value
|
||||||
|
});
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
config: object | any;
|
||||||
|
data: Array<{
|
||||||
|
name: string;
|
||||||
|
value: string | number;
|
||||||
|
}>;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
config: () => { },
|
||||||
|
data: () => [],
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const calcData = () => {
|
||||||
|
mergeConfig();
|
||||||
|
calcCapsuleLengthAndLabelData();
|
||||||
|
};
|
||||||
|
const mergeConfig = () => {
|
||||||
|
mergedConfig.value = merge(cloneDeep(defaultConfig), props.config || {});
|
||||||
|
};
|
||||||
|
const calcCapsuleLengthAndLabelData = () => {
|
||||||
|
if (!props.data.length) return;
|
||||||
|
const newcapsuleValue = props.data.map((item: any) => item.value);
|
||||||
|
const maxValue = Math.max(...newcapsuleValue);
|
||||||
|
capsuleValue.value = newcapsuleValue;
|
||||||
|
capsuleLength.value = newcapsuleValue.map((v: any) =>
|
||||||
|
maxValue ? v / maxValue : 0
|
||||||
|
);
|
||||||
|
const oneFifth = maxValue / 5;
|
||||||
|
const newlabelData = Array.from(
|
||||||
|
new Set(new Array(6).fill(0).map((v, i) => Math.ceil(i * oneFifth)))
|
||||||
|
);
|
||||||
|
labelData.value = newlabelData;
|
||||||
|
// labelDataLength.value = Array.from(newlabelData).map((v) =>
|
||||||
|
// maxValue ? v / maxValue : 0
|
||||||
|
// );
|
||||||
|
// console.log(labelDataLength.value);
|
||||||
|
};
|
||||||
|
watch(
|
||||||
|
() => props.data,
|
||||||
|
(newval: any) => {
|
||||||
|
calcData();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
watch(
|
||||||
|
() => props.config,
|
||||||
|
(newval: any) => {
|
||||||
|
calcData();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
onMounted(() => {
|
||||||
|
calcData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="dv-capsule-chart">
|
||||||
|
<template v-if="mergedConfig">
|
||||||
|
<div class="label-column">
|
||||||
|
<div v-for="item in data" :key="item.name">
|
||||||
|
{{ item.name }}
|
||||||
|
</div>
|
||||||
|
<div> </div>
|
||||||
|
</div>
|
||||||
|
<div class="capsule-container">
|
||||||
|
<div class="capsule-item" v-for="(capsule, index) in capsuleLength" :key="index">
|
||||||
|
<div class="capsule-item-column" :style="`width: ${capsule * 100}%; background-color: ${mergedConfig.colors[index % mergedConfig.colors.length]
|
||||||
|
};`">
|
||||||
|
<div v-if="mergedConfig.showValue" class="capsule-item-value">
|
||||||
|
{{ capsuleValue[index] }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="unit-label">
|
||||||
|
<div v-for="(label, index) in labelData" :key="label + index">
|
||||||
|
{{ label }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="unit-text" v-if="mergedConfig.unit">
|
||||||
|
{{ mergedConfig.unit }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.dv-capsule-chart {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 10px;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
.label-column {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding-right: 10px;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 12px;
|
||||||
|
|
||||||
|
div {
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.capsule-container {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.capsule-item {
|
||||||
|
box-shadow: 0 0 3px #999;
|
||||||
|
height: 10px;
|
||||||
|
margin: 5px 0px;
|
||||||
|
border-radius: 5px;
|
||||||
|
|
||||||
|
.capsule-item-column {
|
||||||
|
position: relative;
|
||||||
|
height: 8px;
|
||||||
|
margin-top: 1px;
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.capsule-item-value {
|
||||||
|
font-size: 12px;
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit-label {
|
||||||
|
height: 20px;
|
||||||
|
font-size: 12px;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unit-text {
|
||||||
|
text-align: right;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 20px;
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,6 @@
|
||||||
|
export interface DefaultConfigType {
|
||||||
|
|
||||||
|
colors: Array<String>;
|
||||||
|
unit:string,
|
||||||
|
showValue:Boolean
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
import CapsuleChart from "./capsule-chart.vue"
|
||||||
|
export * from "./index.d"
|
||||||
|
export default CapsuleChart
|
|
@ -0,0 +1,9 @@
|
||||||
|
<script setup lang="ts"></script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
|
@ -0,0 +1,2 @@
|
||||||
|
import EmptyCom from "./empty-com.vue"
|
||||||
|
export default EmptyCom
|
|
@ -0,0 +1,2 @@
|
||||||
|
import ItemWrap from "./item-wrap.vue"
|
||||||
|
export default ItemWrap
|
|
@ -0,0 +1,81 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import BorderBox13 from "@/components/datav/border-box-13";
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
// 标题
|
||||||
|
title: number | string;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
title: "",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<BorderBox13>
|
||||||
|
<div class="item_title" v-if="title !== ''">
|
||||||
|
<div class="zuo"></div>
|
||||||
|
<span class="title-inner"> {{ title }} </span>
|
||||||
|
<div class="you"></div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
:class="title !== '' ? 'item_title_content' : 'item_title_content_def'"
|
||||||
|
>
|
||||||
|
<slot></slot></div
|
||||||
|
></BorderBox13>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
$item-title-height: 38px;
|
||||||
|
$item_title_content-height: calc(100% - 38px);
|
||||||
|
|
||||||
|
.item_title {
|
||||||
|
height: $item-title-height;
|
||||||
|
line-height: $item-title-height;
|
||||||
|
width: 100%;
|
||||||
|
color: #31abe3;
|
||||||
|
text-align: center;
|
||||||
|
// background: linear-gradient(to right, transparent, #0f0756, transparent);
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
.zuo,
|
||||||
|
.you {
|
||||||
|
width: 58px;
|
||||||
|
height: 14px;
|
||||||
|
background-image: url("@/assets/img/titles/zuo.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
.you {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
.title-inner {
|
||||||
|
font-weight: 900;
|
||||||
|
letter-spacing: 2px;
|
||||||
|
background: linear-gradient(
|
||||||
|
92deg,
|
||||||
|
#0072ff 0%,
|
||||||
|
#00eaff 48.8525390625%,
|
||||||
|
#01aaff 100%
|
||||||
|
);
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.dv-border-box-content) {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 6px 16px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item_title_content {
|
||||||
|
height: $item_title_content-height;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item_title_content_def {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,3 @@
|
||||||
|
import ScaleScreen from './scale-screen.vue'
|
||||||
|
|
||||||
|
export default ScaleScreen
|
|
@ -0,0 +1,246 @@
|
||||||
|
<template>
|
||||||
|
<section
|
||||||
|
:style="{ ...styles.box, ...boxStyle }"
|
||||||
|
class="v-screen-box"
|
||||||
|
ref="box"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
:style="{ ...styles.wrapper, ...wrapperStyle }"
|
||||||
|
class="screen-wrapper"
|
||||||
|
ref="screenWrapper"
|
||||||
|
>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</template>
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { nextTick, onMounted, onUnmounted, reactive, ref, watch } from "vue";
|
||||||
|
import type { CSSProperties, PropType } from "vue";
|
||||||
|
/**
|
||||||
|
* 防抖函数
|
||||||
|
* @param {Function} fn
|
||||||
|
* @param {number} delay
|
||||||
|
* @returns {() => void}
|
||||||
|
*/
|
||||||
|
function debounce(fn: Function, delay: number): () => void {
|
||||||
|
// let timer: NodeJS.Timer;
|
||||||
|
let timer: any;
|
||||||
|
return function (...args: any[]): void {
|
||||||
|
if (timer) clearTimeout(timer);
|
||||||
|
timer = setTimeout(
|
||||||
|
() => {
|
||||||
|
typeof fn === "function" && fn.apply(null, args);
|
||||||
|
clearTimeout(timer);
|
||||||
|
},
|
||||||
|
delay > 0 ? delay : 100
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IState {
|
||||||
|
originalWidth: string | number;
|
||||||
|
originalHeight: string | number;
|
||||||
|
width?: string | number;
|
||||||
|
height?: string | number;
|
||||||
|
observer: null | MutationObserver;
|
||||||
|
}
|
||||||
|
type IAutoScale =
|
||||||
|
| boolean
|
||||||
|
| {
|
||||||
|
x?: boolean;
|
||||||
|
y?: boolean;
|
||||||
|
};
|
||||||
|
const props = defineProps({
|
||||||
|
width: {
|
||||||
|
type: [String, Number] as PropType<string | number>,
|
||||||
|
default: 1920,
|
||||||
|
},
|
||||||
|
height: {
|
||||||
|
type: [String, Number] as PropType<string | number>,
|
||||||
|
default: 1080,
|
||||||
|
},
|
||||||
|
fullScreen: {
|
||||||
|
type: Boolean as PropType<boolean>,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
autoScale: {
|
||||||
|
type: [Object, Boolean] as PropType<IAutoScale>,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
delay: {
|
||||||
|
type: Number as PropType<number>,
|
||||||
|
default: 500,
|
||||||
|
},
|
||||||
|
boxStyle: {
|
||||||
|
type: Object as PropType<CSSProperties>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
wrapperStyle: {
|
||||||
|
type: Object as PropType<CSSProperties>,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const state = reactive<IState>({
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
originalWidth: 0,
|
||||||
|
originalHeight: 0,
|
||||||
|
observer: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
const styles: Record<string, CSSProperties> = {
|
||||||
|
box: {
|
||||||
|
overflow: "hidden",
|
||||||
|
backgroundSize: `100% 100%`,
|
||||||
|
background: `#000`,
|
||||||
|
width: `100vw`,
|
||||||
|
height: `100vh`,
|
||||||
|
},
|
||||||
|
wrapper: {
|
||||||
|
transitionProperty: `all`,
|
||||||
|
transitionTimingFunction: `cubic-bezier(0.4, 0, 0.2, 1)`,
|
||||||
|
transitionDuration: `500ms`,
|
||||||
|
position: `relative`,
|
||||||
|
overflow: `hidden`,
|
||||||
|
zIndex: 100,
|
||||||
|
transformOrigin: `left top`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const screenWrapper = ref<HTMLElement>();
|
||||||
|
const box = ref<HTMLElement>();
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.autoScale,
|
||||||
|
async (newVal: any) => {
|
||||||
|
if (newVal) {
|
||||||
|
onResize();
|
||||||
|
addListener();
|
||||||
|
} else {
|
||||||
|
clearListener();
|
||||||
|
clearScreenWrapperStyle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
/**
|
||||||
|
* 初始化大屏容器宽高
|
||||||
|
*/
|
||||||
|
const initSize = () => {
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
box.value!.scrollLeft = 0;
|
||||||
|
box.value!.scrollTop = 0;
|
||||||
|
nextTick(() => {
|
||||||
|
// region 获取大屏真实尺寸
|
||||||
|
if (props.width && props.height) {
|
||||||
|
state.width = props.width;
|
||||||
|
state.height = props.height;
|
||||||
|
} else {
|
||||||
|
state.width = screenWrapper.value?.clientWidth;
|
||||||
|
state.height = screenWrapper.value?.clientHeight;
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region 获取画布尺寸
|
||||||
|
if (!state.originalHeight || !state.originalWidth) {
|
||||||
|
state.originalWidth = window.screen.width;
|
||||||
|
state.originalHeight = window.screen.height;
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新大屏容器宽高
|
||||||
|
*/
|
||||||
|
const updateSize = () => {
|
||||||
|
if (state.width && state.height) {
|
||||||
|
screenWrapper.value!.style.width = `${state.width}px`;
|
||||||
|
screenWrapper.value!.style.height = `${state.height}px`;
|
||||||
|
} else {
|
||||||
|
screenWrapper.value!.style.width = `${state.originalWidth}px`;
|
||||||
|
screenWrapper.value!.style.height = `${state.originalHeight}px`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const clearScreenWrapperStyle = () => {
|
||||||
|
screenWrapper.value!.style.transform = "";
|
||||||
|
screenWrapper.value!.style.margin = "";
|
||||||
|
};
|
||||||
|
const autoScale = (scale: number) => {
|
||||||
|
if (!props.autoScale) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const domWidth = screenWrapper.value!.clientWidth;
|
||||||
|
const domHeight = screenWrapper.value!.clientHeight;
|
||||||
|
const currentWidth = document.body.clientWidth;
|
||||||
|
const currentHeight = document.body.clientHeight;
|
||||||
|
screenWrapper.value!.style.transform = `scale(${scale},${scale})`;
|
||||||
|
let mx = Math.max((currentWidth - domWidth * scale) / 2, 0);
|
||||||
|
let my = Math.max((currentHeight - domHeight * scale) / 2, 0);
|
||||||
|
if (typeof props.autoScale === "object") {
|
||||||
|
!props.autoScale.x && (mx = 0);
|
||||||
|
!props.autoScale.y && (my = 0);
|
||||||
|
}
|
||||||
|
screenWrapper.value!.style.margin = `${my}px ${mx}px`;
|
||||||
|
};
|
||||||
|
const updateScale = () => {
|
||||||
|
// 获取真实视口尺寸
|
||||||
|
const currentWidth = document.body.clientWidth;
|
||||||
|
const currentHeight = document.body.clientHeight;
|
||||||
|
// 获取大屏最终的宽高
|
||||||
|
const realWidth = state.width || state.originalWidth;
|
||||||
|
const realHeight = state.height || state.originalHeight;
|
||||||
|
// 计算缩放比例
|
||||||
|
const widthScale = currentWidth / +realWidth;
|
||||||
|
const heightScale = currentHeight / +realHeight;
|
||||||
|
// 若要铺满全屏,则按照各自比例缩放
|
||||||
|
if (props.fullScreen) {
|
||||||
|
screenWrapper.value!.style.transform = `scale(${widthScale},${heightScale})`;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// 按照宽高最小比例进行缩放
|
||||||
|
const scale = Math.min(widthScale, heightScale);
|
||||||
|
autoScale(scale);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onResize = debounce(async () => {
|
||||||
|
await initSize();
|
||||||
|
updateSize();
|
||||||
|
updateScale();
|
||||||
|
}, props.delay);
|
||||||
|
const initMutationObserver = () => {
|
||||||
|
const observer = (state.observer = new MutationObserver(() => {
|
||||||
|
onResize();
|
||||||
|
}));
|
||||||
|
observer.observe(screenWrapper.value!, {
|
||||||
|
attributes: true,
|
||||||
|
attributeFilter: ["style"],
|
||||||
|
attributeOldValue: true,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearListener = () => {
|
||||||
|
window.removeEventListener("resize", onResize);
|
||||||
|
// state.observer?.disconnect();
|
||||||
|
};
|
||||||
|
|
||||||
|
const addListener = () => {
|
||||||
|
window.addEventListener("resize", onResize);
|
||||||
|
// initMutationObserver();
|
||||||
|
};
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(async () => {
|
||||||
|
await initSize();
|
||||||
|
updateSize();
|
||||||
|
updateScale();
|
||||||
|
addListener();
|
||||||
|
// initMutationObserver();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearListener();
|
||||||
|
// state.observer?.disconnect();
|
||||||
|
});
|
||||||
|
</script>
|
|
@ -0,0 +1,2 @@
|
||||||
|
import SeamlessScroll from "./seamless-scroll.vue"
|
||||||
|
export default SeamlessScroll
|
|
@ -0,0 +1,407 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import {
|
||||||
|
computed,
|
||||||
|
defineComponent,
|
||||||
|
onBeforeMount,
|
||||||
|
onMounted,
|
||||||
|
ref,
|
||||||
|
watch,
|
||||||
|
nextTick,
|
||||||
|
} from "vue";
|
||||||
|
import type { CSSProperties } from "vue";
|
||||||
|
import throttle from "lodash/throttle";
|
||||||
|
type propsType = {
|
||||||
|
modelValue?: boolean;
|
||||||
|
list: Array<any>;
|
||||||
|
step?: number;
|
||||||
|
limitScrollNum?: number;
|
||||||
|
hover?: boolean;
|
||||||
|
direction?: string;
|
||||||
|
singleHeight?: number;
|
||||||
|
singleWidth?: number;
|
||||||
|
singleWaitTime?: number;
|
||||||
|
isRemUnit?: boolean;
|
||||||
|
isWatch?: boolean;
|
||||||
|
delay?: number;
|
||||||
|
ease?: any;
|
||||||
|
count?: number;
|
||||||
|
copyNum?: number;
|
||||||
|
wheel?: boolean;
|
||||||
|
singleLine?: boolean;
|
||||||
|
};
|
||||||
|
const props = withDefaults(defineProps<propsType>(), {
|
||||||
|
// 是否开启自动滚动
|
||||||
|
modelValue: true,
|
||||||
|
// 原始数据列表
|
||||||
|
list: () => [],
|
||||||
|
// 步进速度,step 需是单步大小的约数
|
||||||
|
step: 1,
|
||||||
|
// 开启滚动的数据量
|
||||||
|
limitScrollNum: 3,
|
||||||
|
// 是否开启鼠标悬停
|
||||||
|
hover: false,
|
||||||
|
// 控制滚动方向
|
||||||
|
direction: "up",
|
||||||
|
// 单步运动停止的高度
|
||||||
|
singleHeight: 0,
|
||||||
|
// 单步运动停止的宽度
|
||||||
|
singleWidth: 0,
|
||||||
|
// 单步停止等待时间 (默认值 1000ms)
|
||||||
|
singleWaitTime: 1000,
|
||||||
|
// 是否开启 rem 度量
|
||||||
|
isRemUnit: false,
|
||||||
|
// 开启数据更新监听
|
||||||
|
isWatch: true,
|
||||||
|
// 动画时间
|
||||||
|
delay: 0,
|
||||||
|
// 动画方式
|
||||||
|
ease: "ease-in",
|
||||||
|
// 动画循环次数,-1 表示一直动画
|
||||||
|
count: -1,
|
||||||
|
// 拷贝几份滚动列表
|
||||||
|
copyNum: 1,
|
||||||
|
// 开启鼠标悬停时支持滚轮滚动
|
||||||
|
wheel: false,
|
||||||
|
// 启用单行滚动
|
||||||
|
singleLine: false,
|
||||||
|
});
|
||||||
|
interface Emits {
|
||||||
|
(event: "count", _count: number): void;
|
||||||
|
(event: "stop", _count: number): void;
|
||||||
|
}
|
||||||
|
const emit = defineEmits<Emits>();
|
||||||
|
const scrollRef = ref(null);
|
||||||
|
const slotListRef = ref<HTMLDivElement | null>(null);
|
||||||
|
const realBoxRef = ref<HTMLDivElement | null>(null);
|
||||||
|
const reqFrame = ref<number | null>(null);
|
||||||
|
const singleWaitTimeout = ref<TimeProp | null>(null);
|
||||||
|
const realBoxWidth = ref(0);
|
||||||
|
const realBoxHeight = ref(0);
|
||||||
|
const xPos = ref(0);
|
||||||
|
const yPos = ref(0);
|
||||||
|
const isHover = ref(false);
|
||||||
|
const _count = ref(0);
|
||||||
|
const isScroll = computed(() =>
|
||||||
|
props.list ? props.list.length >= props.limitScrollNum : false
|
||||||
|
);
|
||||||
|
const realBoxStyle = computed(() => {
|
||||||
|
return {
|
||||||
|
width: realBoxWidth.value ? `${realBoxWidth.value}px` : "auto",
|
||||||
|
transform: `translate(${xPos.value}px,${yPos.value}px)`,
|
||||||
|
transition: `all ${
|
||||||
|
typeof props.ease === "string"
|
||||||
|
? props.ease
|
||||||
|
: "cubic-bezier(" +
|
||||||
|
props.ease.x1 +
|
||||||
|
"," +
|
||||||
|
props.ease.y1 +
|
||||||
|
"," +
|
||||||
|
props.ease.x2 +
|
||||||
|
"," +
|
||||||
|
props.ease.y2 +
|
||||||
|
")"
|
||||||
|
} ${props.delay}ms`,
|
||||||
|
overflow: "hidden",
|
||||||
|
display: props.singleLine ? "flex" : "block",
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const isHorizontal = computed(
|
||||||
|
() => props.direction == "left" || props.direction == "right"
|
||||||
|
);
|
||||||
|
|
||||||
|
function dataWarm(list: any) {
|
||||||
|
if (list && typeof list !== "boolean" && list.length > 100) {
|
||||||
|
console.warn(
|
||||||
|
`数据达到了${list.length}条有点多哦~,可能会造成部分老旧浏览器卡顿。`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const floatStyle = computed<CSSProperties>(() => {
|
||||||
|
return isHorizontal.value
|
||||||
|
? {
|
||||||
|
float: "left",
|
||||||
|
overflow: "hidden",
|
||||||
|
display: props.singleLine ? "flex" : "block",
|
||||||
|
flexShrink: props.singleLine ? 0 : 1,
|
||||||
|
}
|
||||||
|
: { overflow: "hidden" };
|
||||||
|
});
|
||||||
|
const baseFontSize = computed(() => {
|
||||||
|
return props.isRemUnit
|
||||||
|
? parseInt(
|
||||||
|
globalThis.window.getComputedStyle(
|
||||||
|
globalThis.document.documentElement,
|
||||||
|
null
|
||||||
|
).fontSize
|
||||||
|
)
|
||||||
|
: 1;
|
||||||
|
});
|
||||||
|
const realSingleStopWidth = computed(
|
||||||
|
() => props.singleWidth * baseFontSize.value
|
||||||
|
);
|
||||||
|
|
||||||
|
const realSingleStopHeight = computed(
|
||||||
|
() => props.singleHeight * baseFontSize.value
|
||||||
|
);
|
||||||
|
|
||||||
|
const step = computed(() => {
|
||||||
|
let singleStep: number;
|
||||||
|
let _step = props.step;
|
||||||
|
if (isHorizontal.value) {
|
||||||
|
singleStep = realSingleStopWidth.value;
|
||||||
|
} else {
|
||||||
|
singleStep = realSingleStopHeight.value;
|
||||||
|
}
|
||||||
|
if (singleStep > 0 && singleStep % _step > 0) {
|
||||||
|
console.error(
|
||||||
|
"如果设置了单步滚动,step 需是单步大小的约数,否则无法保证单步滚动结束的位置是否准确。~~~~~"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return _step;
|
||||||
|
});
|
||||||
|
|
||||||
|
const cancle = () => {
|
||||||
|
cancelAnimationFrame(reqFrame.value as number);
|
||||||
|
reqFrame.value = null;
|
||||||
|
};
|
||||||
|
const animation = (
|
||||||
|
_direction: "up" | "down" | "left" | "right",
|
||||||
|
_step: number,
|
||||||
|
isWheel?: boolean
|
||||||
|
) => {
|
||||||
|
// console.log("animation",_direction,_step,isWheel);
|
||||||
|
reqFrame.value = requestAnimationFrame(function () {
|
||||||
|
const h = realBoxHeight.value / 2;
|
||||||
|
const w = realBoxWidth.value / 2;
|
||||||
|
if (_direction === "up") {
|
||||||
|
if (Math.abs(yPos.value) >= h) {
|
||||||
|
yPos.value = 0;
|
||||||
|
_count.value += 1;
|
||||||
|
emit("count", _count.value);
|
||||||
|
}
|
||||||
|
yPos.value -= _step;
|
||||||
|
} else if (_direction === "down") {
|
||||||
|
if (yPos.value >= 0) {
|
||||||
|
yPos.value = h * -1;
|
||||||
|
_count.value += 1;
|
||||||
|
emit("count", _count.value);
|
||||||
|
}
|
||||||
|
yPos.value += _step;
|
||||||
|
} else if (_direction === "left") {
|
||||||
|
if (Math.abs(xPos.value) >= w) {
|
||||||
|
xPos.value = 0;
|
||||||
|
_count.value += 1;
|
||||||
|
emit("count", _count.value);
|
||||||
|
}
|
||||||
|
xPos.value -= _step;
|
||||||
|
} else if (_direction === "right") {
|
||||||
|
if (xPos.value >= 0) {
|
||||||
|
xPos.value = w * -1;
|
||||||
|
_count.value += 1;
|
||||||
|
emit("count", _count.value);
|
||||||
|
}
|
||||||
|
xPos.value += _step;
|
||||||
|
}
|
||||||
|
if (isWheel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let { singleWaitTime } = props;
|
||||||
|
if (singleWaitTimeout.value) {
|
||||||
|
clearTimeout(singleWaitTimeout.value);
|
||||||
|
}
|
||||||
|
if (!!realSingleStopHeight.value) {
|
||||||
|
if (Math.abs(yPos.value) % realSingleStopHeight.value < _step) {
|
||||||
|
singleWaitTimeout.value = setTimeout(() => {
|
||||||
|
move();
|
||||||
|
}, singleWaitTime);
|
||||||
|
} else {
|
||||||
|
move();
|
||||||
|
}
|
||||||
|
} else if (!!realSingleStopWidth.value) {
|
||||||
|
if (Math.abs(xPos.value) % realSingleStopWidth.value < _step) {
|
||||||
|
singleWaitTimeout.value = setTimeout(() => {
|
||||||
|
move();
|
||||||
|
}, singleWaitTime);
|
||||||
|
} else {
|
||||||
|
move();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
move();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const move = () => {
|
||||||
|
cancle();
|
||||||
|
if (isHover.value || !isScroll.value || _count.value === props.count) {
|
||||||
|
emit("stop", _count.value);
|
||||||
|
_count.value = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
animation(
|
||||||
|
props.direction as "up" | "down" | "left" | "right",
|
||||||
|
step.value,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const initMove = () => {
|
||||||
|
dataWarm(props.list);
|
||||||
|
if (isHorizontal.value) {
|
||||||
|
let slotListWidth = (slotListRef.value as HTMLDivElement).offsetWidth;
|
||||||
|
slotListWidth = slotListWidth * 2 + 1;
|
||||||
|
realBoxWidth.value = slotListWidth;
|
||||||
|
}
|
||||||
|
if (isScroll.value) {
|
||||||
|
realBoxHeight.value = (realBoxRef.value as HTMLDivElement).offsetHeight;
|
||||||
|
if (props.modelValue) {
|
||||||
|
move();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cancle();
|
||||||
|
yPos.value = xPos.value = 0;
|
||||||
|
}
|
||||||
|
// console.log("initMove","isHorizontal",isHorizontal.value,"isScroll",isScroll.value,realBoxRef.value?.offsetHeight);
|
||||||
|
};
|
||||||
|
const startMove = () => {
|
||||||
|
isHover.value = false;
|
||||||
|
move();
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopMove = () => {
|
||||||
|
isHover.value = true;
|
||||||
|
if (singleWaitTimeout.value) {
|
||||||
|
clearTimeout(singleWaitTimeout.value);
|
||||||
|
}
|
||||||
|
cancle();
|
||||||
|
};
|
||||||
|
|
||||||
|
const hoverStop = computed(
|
||||||
|
() => props.hover && props.modelValue && isScroll.value
|
||||||
|
);
|
||||||
|
const throttleFunc = throttle((e: WheelEvent) => {
|
||||||
|
cancle();
|
||||||
|
const singleHeight = !!realSingleStopHeight.value
|
||||||
|
? realSingleStopHeight.value
|
||||||
|
: 15;
|
||||||
|
if (e.deltaY < 0) {
|
||||||
|
animation("down", singleHeight, true);
|
||||||
|
}
|
||||||
|
if (e.deltaY > 0) {
|
||||||
|
animation("up", singleHeight, true);
|
||||||
|
}
|
||||||
|
}, 30);
|
||||||
|
|
||||||
|
const onWheel = (e: WheelEvent) => {
|
||||||
|
throttleFunc(e);
|
||||||
|
};
|
||||||
|
const reset = () => {
|
||||||
|
cancle();
|
||||||
|
isHover.value = false;
|
||||||
|
initMove();
|
||||||
|
};
|
||||||
|
const Reset = () => {
|
||||||
|
reset();
|
||||||
|
};
|
||||||
|
defineExpose({
|
||||||
|
Reset,
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.list,
|
||||||
|
() => {
|
||||||
|
if (props.isWatch) {
|
||||||
|
nextTick(() => {
|
||||||
|
reset();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(newValue) => {
|
||||||
|
if (newValue) {
|
||||||
|
startMove();
|
||||||
|
} else {
|
||||||
|
stopMove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.count,
|
||||||
|
(newValue) => {
|
||||||
|
if (newValue !== 0) {
|
||||||
|
startMove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
cancle();
|
||||||
|
clearTimeout(singleWaitTimeout.value as unknown as number);
|
||||||
|
});
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (isScroll.value) {
|
||||||
|
initMove();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
v-if="props.wheel && props.hover"
|
||||||
|
ref="realBoxRef"
|
||||||
|
:style="realBoxStyle"
|
||||||
|
@mouseenter="
|
||||||
|
() => {
|
||||||
|
hoverStop && stopMove();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@mouseleave="
|
||||||
|
() => {
|
||||||
|
hoverStop && startMove();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@wheel="
|
||||||
|
(e) => {
|
||||||
|
hoverStop && onWheel(e);
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div ref="slotListRef" :style="floatStyle">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
<div :style="floatStyle">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-else
|
||||||
|
:style="realBoxStyle"
|
||||||
|
ref="realBoxRef"
|
||||||
|
@mouseenter="
|
||||||
|
() => {
|
||||||
|
hoverStop && stopMove();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
@mouseleave="
|
||||||
|
() => {
|
||||||
|
hoverStop && startMove();
|
||||||
|
}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div ref="slotListRef" :style="floatStyle">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
<div :style="floatStyle">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
|
@ -0,0 +1,32 @@
|
||||||
|
interface UtilVarType {
|
||||||
|
baseUrl:string,
|
||||||
|
code:string|number,
|
||||||
|
noContentCode:number,
|
||||||
|
ENC:boolean,//是否进行加密
|
||||||
|
}
|
||||||
|
|
||||||
|
const UtilVar:UtilVarType = {
|
||||||
|
baseUrl:"http://api.nclg.yx.zheke.com",
|
||||||
|
code:401, //登陆过期
|
||||||
|
noContentCode:204, //请求成功但没有内容
|
||||||
|
ENC:false,
|
||||||
|
|
||||||
|
}
|
||||||
|
const runtimeType:any = {
|
||||||
|
|
||||||
|
production: () => {
|
||||||
|
UtilVar.baseUrl= `http://api.nclg.yx.zheke.com`
|
||||||
|
},
|
||||||
|
//开发环境
|
||||||
|
development: () => {
|
||||||
|
UtilVar.baseUrl= `http://api.nclg.yx.zheke.com`
|
||||||
|
|
||||||
|
},
|
||||||
|
hash:()=>{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// console.log(import.meta.env)
|
||||||
|
runtimeType[import.meta.env.MODE]&&runtimeType[import.meta.env.MODE]()
|
||||||
|
export default UtilVar
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./storage-enum"
|
||||||
|
export * from "./request-enums"
|
|
@ -0,0 +1,12 @@
|
||||||
|
export enum RequestEnum {
|
||||||
|
// token key
|
||||||
|
GB_TOKEN_KEY = 'auth-token',
|
||||||
|
// 验签key
|
||||||
|
GB_SIGN_KEY = "sign",
|
||||||
|
// 时间戳 key
|
||||||
|
GB_TIMESTAMP_KEY = "timestamp"
|
||||||
|
}
|
||||||
|
export enum ReqCodeEnum {
|
||||||
|
Unauthorized = 401,// token过期
|
||||||
|
Success = 200,//成功
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
export enum StorageEnum {
|
||||||
|
// token
|
||||||
|
GB_TOKEN_STORE = 'GB_TOKEN_STORE',
|
||||||
|
|
||||||
|
// 语言
|
||||||
|
YH_LANG_STORE = 'YH_LANG',
|
||||||
|
//皮肤
|
||||||
|
YH_THEME_STORE = 'YH_THEME',
|
||||||
|
}
|